Skip to content

Commit 98633c9

Browse files
committed
internal/llsync: 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 c5d4cf3 commit 98633c9

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

src/internal/llsync/atomic-cooperative.go

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

0 commit comments

Comments
 (0)