Skip to content

Commit 6371e67

Browse files
committed
internal/task: add cooperative implementation of Futex
See the code comments for details. But in short, this implements a futex for the cooperative scheduler (that is single threaded and non-reentrant). Similar implementations can be made for basically every other operating system, and even WebAssembly with the threading (actually: atomics) proposal. Using this basic futex implementation means we can use the same implementation for synchronisation primitives on cooperative and multicore systems. For more information on futex across operating systems: https://outerproduct.net/futex-dictionary.html
1 parent 327039c commit 6371e67

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

src/internal/task/atomic-cooperative.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package task
55
// different goroutine or interrupt happening at the same time.
66

77
type atomicIntegerType interface {
8-
uintptr | uint64
8+
uintptr | uint32 | uint64
99
}
1010

1111
type pseudoAtomic[T atomicIntegerType] struct {
@@ -32,6 +32,10 @@ func (x *pseudoAtomic[T]) Swap(new T) (old T) {
3232
// uintptr otherwise.
3333
type Uintptr = pseudoAtomic[uintptr]
3434

35+
// Uint32 is an atomic uint32 when multithreading is enabled, and a plain old
36+
// uint32 otherwise.
37+
type Uint32 = pseudoAtomic[uint32]
38+
3539
// Uint64 is an atomic uint64 when multithreading is enabled, and a plain old
3640
// uint64 otherwise.
3741
type Uint64 = pseudoAtomic[uint64]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package task
2+
3+
import (
4+
_ "unsafe" // for //go:linkname
5+
)
6+
7+
// A futex is a way for userspace to wait with the pointer as the key, and for
8+
// another thread to wake one or all waiting threads keyed on the same pointer.
9+
//
10+
// A futex does not change the underlying value, it only reads it before to prevent
11+
// lost wake-ups.
12+
type Futex struct {
13+
Uint32
14+
waiters Stack
15+
}
16+
17+
// Atomically check for cmp to still be equal to the futex value and if so, go
18+
// to sleep. Return true if we were definitely awoken by a call to Wake or
19+
// WakeAll, and false if we can't be sure of that.
20+
func (f *Futex) Wait(cmp uint32) (awoken bool) {
21+
if f.Uint32.v != cmp {
22+
return false
23+
}
24+
25+
// Push the current goroutine onto the waiter stack.
26+
f.waiters.Push(Current())
27+
28+
// Pause until the waiters are awoken by Wake/WakeAll.
29+
Pause()
30+
31+
// We were awoken by a call to Wake or WakeAll. There is no chance for
32+
// spurious wakeups.
33+
return true
34+
}
35+
36+
// Wake a single waiter.
37+
func (f *Futex) Wake() {
38+
if t := f.waiters.Pop(); t != nil {
39+
resumeTask(t)
40+
}
41+
}
42+
43+
// Wake all waiters.
44+
func (f *Futex) WakeAll() {
45+
for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() {
46+
resumeTask(t)
47+
}
48+
}
49+
50+
//go:linkname resumeTask runtime.resumeTask
51+
func resumeTask(*Task)

0 commit comments

Comments
 (0)