-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
content: add example of zero-cost-thread-sched
- Loading branch information
Showing
8 changed files
with
441 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
80
content/assets/zero-cost-thread-sched/mainthread/mainthread.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{} | ||
} |
57 changes: 57 additions & 0 deletions
57
content/assets/zero-cost-thread-sched/mainthread/mainthread_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.