Skip to content

Commit

Permalink
content: add example of zero-cost-thread-sched
Browse files Browse the repository at this point in the history
  • Loading branch information
changkun committed Jan 21, 2021
1 parent fd3f62f commit bfca30e
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 0 deletions.
67 changes: 67 additions & 0 deletions content/assets/zero-cost-thread-sched/app/window.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2021 The golang.design Initiative Authors.
// All rights reserved.
//
// The code below is produced by Changkun Ou <[email protected]>.

package app

import (
"x/mainthread"

"github.com/go-gl/glfw/v3.3/glfw"
)

// Win is a window.
type Win struct {
win *glfw.Window
}

// NewWindow constructs a new graphical window.
func NewWindow() (*Win, error) {
var (
w = &Win{}
err error
)
mainthread.Call(func() {
err = glfw.Init()
if err != nil {
return
}

w.win, err = glfw.CreateWindow(640, 480, "win", nil, nil)
if err != nil {
return
}
})
return w, nil
}

// Terminate terminates the entire application.
func Terminate() {
mainthread.Call(func() {
glfw.Terminate()
})
}

// Closed asserts if the given window is closed.
func (w *Win) Closed() (stop bool) {
return mainthread.CallV(func() interface{} {
return w.win.ShouldClose()
}).(bool)
}

// Update updates the frame buffer of the given window.
func (w *Win) Update() {
mainthread.Call(func() {
w.win.SwapBuffers()
// glfw.WaitEventsTimeout(1.0 / 30)
glfw.PollEvents()
})
}

// Stop stops the given window.
func (w *Win) Stop() {
mainthread.Call(func() {
w.win.SetShouldClose(true)
})
}
8 changes: 8 additions & 0 deletions content/assets/zero-cost-thread-sched/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module x

go 1.16

require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
)
4 changes: 4 additions & 0 deletions content/assets/zero-cost-thread-sched/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265 h1:BcbKYUZo/TKPsiSh7LymK3p+TNAJJW3OfGO/21sBbiA=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
61 changes: 61 additions & 0 deletions content/assets/zero-cost-thread-sched/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2021 The golang.design Initiative Authors.
// All rights reserved.
//
// The code below is produced by Changkun Ou <[email protected]>.

package main

import (
"flag"
"fmt"
"os"
"runtime/trace"
"time"
"x/app"
"x/mainthread"
)

func main() {
mainthread.Init(fn)
}

func fn() {
run := flag.Bool("run", false, "start test")
traceF := flag.String("trace", "trace.out", "trace file, default: trace.out")
traceT := flag.String("d", "10s", "trace duration, default: 10s")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `usage: go run main.go -run [-trace FILENAME -d DURATION]
options:
`)
flag.PrintDefaults()
}
flag.Parse()
if !*run {
flag.Usage()
os.Exit(2)
}

d, err := time.ParseDuration(*traceT)
if err != nil {
flag.Usage()
os.Exit(2)
}

w, err := app.NewWindow()
if err != nil {
panic(err)
}
defer app.Terminate()

go func() {
f, _ := os.Create(*traceF)
defer f.Close()
trace.Start(f)
defer trace.Stop()
time.Sleep(d)
w.Stop()
}()
for !w.Closed() {
w.Update()
}
}
80 changes: 80 additions & 0 deletions content/assets/zero-cost-thread-sched/mainthread/mainthread.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2021 The golang.design Initiative Authors.
// All rights reserved.
//
// The code below is produced by Changkun Ou <[email protected]>.

package mainthread

import (
"runtime"
"sync"
)

func init() {
runtime.LockOSThread()
}

// Init initializes the functionality of running arbitrary subsequent
// functions be called on the main system thread.
//
// Init must be called in the main.main function.
func Init(main func()) {
done := donePool.Get().(chan struct{})
defer donePool.Put(done)

go func() {
defer func() {
done <- struct{}{}
}()
main()
}()

for {
select {
case f := <-funcQ:
if f.fn != nil {
f.fn()
f.done <- struct{}{}
} else if f.fnv != nil {
f.ret <- f.fnv()
}
case <-done:
return
}
}
}

// Call calls f on the main thread and blocks until f finishes.
func Call(f func()) {
done := donePool.Get().(chan struct{})
defer donePool.Put(done)

funcQ <- funcData{fn: f, done: done}
<-done
}

// CallV calls f on the main thread. It returns what f returns.
func CallV(f func() interface{}) interface{} {
ret := retPool.Get().(chan interface{})
defer retPool.Put(ret)

funcQ <- funcData{fnv: f, ret: ret}
return <-ret
}

var (
funcQ = make(chan funcData, runtime.GOMAXPROCS(0))
donePool = sync.Pool{New: func() interface{} {
return make(chan struct{})
}}
retPool = sync.Pool{New: func() interface{} {
return make(chan interface{})
}}
)

type funcData struct {
fn func()
done chan struct{}
fnv func() interface{}
ret chan interface{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2021 The golang.design Initiative Authors.
// All rights reserved.
//
// The code below is produced by Changkun Ou <[email protected]>.

package mainthread_test

import (
"testing"

"x/mainthread"
)

/*
bench: run benchmarks under 90% cpufreq...
bench: go test -run=^$ -bench=. -count=10
goos: linux
goarch: amd64
pkg: x/mainthread
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
name time/op
Call-16 391ns ±0%
CallV-16 398ns ±0%
name alloc/op
Call-16 0.00B
CallV-16 0.00B
name allocs/op
Call-16 0.00
CallV-16 0.00
*/

func BenchmarkCall(b *testing.B) {
f := func() {}
mainthread.Init(func() {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
mainthread.Call(f)
}
})
}

func BenchmarkCallV(b *testing.B) {
f := func() interface{} {
return true
}
mainthread.Init(func() {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = mainthread.CallV(f).(bool)
}
})
}
89 changes: 89 additions & 0 deletions content/assets/zero-cost-thread-sched/thread/thread.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2021 The golang.design Initiative Authors.
// All rights reserved.
//
// The code below is produced by Changkun Ou <[email protected]>.

package thread

import (
"runtime"
"sync"
)

var donePool = sync.Pool{
New: func() interface{} {
return make(chan struct{})
},
}

func init() {
runtime.LockOSThread()
}

type funcData struct {
fn func()
done chan struct{}
}

// Thread offers facilities to schedule function calls to run
// on a same thread.
type Thread struct {
f chan funcData
terminate chan struct{}
}

// Call calls f on the given thread.
func (t *Thread) Call(f func()) bool {
if f == nil {
return false
}
select {
case <-t.terminate:
return false
default:
done := donePool.Get().(chan struct{})
defer donePool.Put(done)
defer func() {
<-done
}()

t.f <- funcData{fn: f, done: done}
}
return true
}

// Terminate terminates the current thread.
func (t *Thread) Terminate() {
select {
case <-t.terminate:
return
default:
t.terminate <- struct{}{}
}
}

// New creates
func New() *Thread {
t := Thread{
f: make(chan funcData),
terminate: make(chan struct{}),
}
go func() {
runtime.LockOSThread()
for {
select {
case f := <-t.f:
func() {
defer func() {
f.done <- struct{}{}
}()
f.fn()
}()
case <-t.terminate:
close(t.terminate)
return
}
}
}()
return &t
}
Loading

0 comments on commit bfca30e

Please sign in to comment.