Skip to content

Commit 335e9ad

Browse files
committed
all: initial implementation
1 parent 4c755ad commit 335e9ad

File tree

7 files changed

+464
-2
lines changed

7 files changed

+464
-2
lines changed

.github/workflows/thread.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: thread
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
name: Build
12+
runs-on: ubuntu-latest
13+
steps:
14+
15+
- name: Set up Go 1.x
16+
uses: actions/setup-go@v2
17+
with:
18+
go-version: ^1.13
19+
id: go
20+
21+
- name: Check out code into the Go module directory
22+
uses: actions/checkout@v2
23+
24+
- name: Get dependencies
25+
run: |
26+
go get -v -t -d ./...
27+
- name: Race Test
28+
run: |
29+
go test -v -race -count=1 ./...
30+
- name: Test
31+
run: |
32+
go test -v -coverprofile=coverage.txt -covermode=atomic ./...

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,22 @@
1-
# thread
2-
thead facilities in Go
1+
# thread [![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/thread)](https://pkg.go.dev/golang.design/x/thread) [![Go Report Card](https://goreportcard.com/badge/golang.design/x/thread)](https://goreportcard.com/report/golang.design/x/thread) ![thread](https://github.com/golang-design/thread/workflows/thread/badge.svg?branch=main)
2+
3+
Package thread provides threading facilities, such as scheduling
4+
calls on a specific thread, local storage, etc.
5+
6+
```go
7+
import "golang.design/x/thread"
8+
```
9+
10+
## Quick Start
11+
12+
```go
13+
th := thread.New()
14+
15+
th.Call(func() {
16+
// call on the created thread
17+
})
18+
```
19+
20+
## License
21+
22+
MIT © 2021 The golang.design Initiative

bench_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package thread_test
2+
3+
import (
4+
"testing"
5+
6+
"golang.design/x/thread"
7+
)
8+
9+
func BenchmarkThread_Call(b *testing.B) {
10+
th := thread.New()
11+
b.ReportAllocs()
12+
b.ResetTimer()
13+
14+
for i := 0; i < b.N; i++ {
15+
th.Call(func() {})
16+
}
17+
}
18+
func BenchmarkThread_CallV(b *testing.B) {
19+
th := thread.New()
20+
b.ReportAllocs()
21+
b.ResetTimer()
22+
23+
for i := 0; i < b.N; i++ {
24+
_ = th.CallV(func() interface{} {
25+
return true
26+
}).(bool)
27+
}
28+
}
29+
30+
func BenchmarkThread_TLS(b *testing.B) {
31+
th := thread.New()
32+
th.Call(func() {
33+
th.SetTLS(1)
34+
})
35+
36+
b.ReportAllocs()
37+
b.ResetTimer()
38+
for i := 0; i < b.N; i++ {
39+
_ = th.CallV(func() interface{} {
40+
return th.GetTLS()
41+
})
42+
}
43+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module golang.design/x/thread
2+
3+
go 1.13
4+
5+
require golang.org/x/sys v0.0.0-20210122093101-04d7465088b8

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 h1:de2yTH1xuxjmGB7i6Z5o2z3RCHVa0XlpSZzjd8Fe6bE=
2+
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

thread.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright 2020 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
5+
// Package thread provides threading facilities, such as scheduling
6+
// calls on a specific thread, local storage, etc.
7+
package thread // import "golang.design/x/thread"
8+
9+
import (
10+
"runtime"
11+
"sync"
12+
"sync/atomic"
13+
)
14+
15+
// Thread represents a thread instance.
16+
type Thread interface {
17+
// ID returns the ID of the thread.
18+
ID() uint64
19+
20+
// Call calls fn from the given thread. It blocks until fn returns.
21+
Call(fn func())
22+
23+
// CallNonBlock call fn from the given thread without waiting
24+
// fn to complete.
25+
CallNonBlock(fn func())
26+
27+
// CallV call fn from the given thread and returns the returned
28+
// value from fn.
29+
//
30+
// The purpose of this function is to avoid value escaping.
31+
// In particular:
32+
//
33+
// th := thread.New()
34+
// var ret interface{}
35+
// th.Call(func() {
36+
// ret = 1
37+
// })
38+
//
39+
// will cause variable ret be allocated on the heap, whereas
40+
//
41+
// th := thread.New()
42+
// ret := th.CallV(func() interface{} {
43+
// return 1
44+
// }).(int)
45+
//
46+
// will offer zero allocation benefits.
47+
CallV(fn func() interface{}) interface{}
48+
49+
// SetTLS stores a given value to the local storage of the given
50+
// thread. This method must be accessed in Call, or CallV, or
51+
// CallNonBlock. For instance:
52+
//
53+
// th := thread.New()
54+
// th.Call(func() {
55+
// th.SetTLS("store in thread local storage")
56+
// })
57+
SetTLS(x interface{})
58+
59+
// GetTLS returns the locally stored value from local storage of
60+
// the given thread. This method must be access in Call, or CallV,
61+
// or CallNonBlock. For instance:
62+
//
63+
// th := thread.New()
64+
// th.Call(func() {
65+
// tls := th.GetTLS()
66+
// // ... do what ever you want to do with tls value ...
67+
// })
68+
//
69+
GetTLS() interface{}
70+
71+
// Terminate terminates the given thread gracefully.
72+
// Scheduled but unexecuted calls will be discarded.
73+
Terminate()
74+
}
75+
76+
// New creates a new thread instance.
77+
func New() Thread {
78+
th := thread{
79+
id: atomic.AddUint64(&globalID, 1),
80+
fdCh: make(chan funcData, runtime.GOMAXPROCS(0)),
81+
doneCh: make(chan struct{}),
82+
}
83+
runtime.SetFinalizer(&th, func(th interface{}) {
84+
th.(*thread).Terminate()
85+
})
86+
go func() {
87+
runtime.LockOSThread()
88+
for {
89+
select {
90+
case fd := <-th.fdCh:
91+
func() {
92+
if fd.fn != nil {
93+
defer func() {
94+
if fd.done != nil {
95+
fd.done <- struct{}{}
96+
}
97+
}()
98+
fd.fn()
99+
} else if fd.fnv != nil {
100+
var ret interface{}
101+
defer func() {
102+
if fd.ret != nil {
103+
fd.ret <- ret
104+
}
105+
}()
106+
ret = fd.fnv()
107+
}
108+
}()
109+
case <-th.doneCh:
110+
close(th.doneCh)
111+
return
112+
}
113+
}
114+
}()
115+
return &th
116+
}
117+
118+
var (
119+
donePool = sync.Pool{
120+
New: func() interface{} {
121+
return make(chan struct{})
122+
},
123+
}
124+
varPool = sync.Pool{
125+
New: func() interface{} {
126+
return make(chan interface{})
127+
},
128+
}
129+
globalID uint64 // atomic
130+
_ Thread = &thread{}
131+
)
132+
133+
type funcData struct {
134+
fn func()
135+
done chan struct{}
136+
137+
fnv func() interface{}
138+
ret chan interface{}
139+
}
140+
141+
type thread struct {
142+
id uint64
143+
tls interface{}
144+
145+
fdCh chan funcData
146+
doneCh chan struct{}
147+
}
148+
149+
func (th thread) ID() uint64 {
150+
return th.id
151+
}
152+
153+
func (th *thread) Call(fn func()) {
154+
if fn == nil {
155+
return
156+
}
157+
158+
select {
159+
case <-th.doneCh:
160+
return
161+
default:
162+
done := donePool.Get().(chan struct{})
163+
defer donePool.Put(done)
164+
defer func() { <-done }()
165+
166+
th.fdCh <- funcData{fn: fn, done: done}
167+
}
168+
return
169+
}
170+
171+
func (th *thread) CallNonBlock(fn func()) {
172+
if fn == nil {
173+
return
174+
}
175+
select {
176+
case <-th.doneCh:
177+
return
178+
default:
179+
th.fdCh <- funcData{fn: fn}
180+
}
181+
}
182+
183+
func (th *thread) CallV(fn func() interface{}) (ret interface{}) {
184+
if fn == nil {
185+
return nil
186+
}
187+
188+
select {
189+
case <-th.doneCh:
190+
return nil
191+
default:
192+
done := varPool.Get().(chan interface{})
193+
defer varPool.Put(done)
194+
defer func() { ret = <-done }()
195+
196+
th.fdCh <- funcData{fnv: fn, ret: done}
197+
return
198+
}
199+
}
200+
201+
func (th *thread) GetTLS() interface{} {
202+
return th.tls
203+
}
204+
205+
func (th *thread) SetTLS(x interface{}) {
206+
th.tls = x
207+
}
208+
209+
func (th *thread) Terminate() {
210+
select {
211+
case <-th.doneCh:
212+
return
213+
default:
214+
th.doneCh <- struct{}{}
215+
select {
216+
case <-th.doneCh:
217+
return
218+
}
219+
}
220+
}

0 commit comments

Comments
 (0)