Skip to content

Commit b9d285d

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 98eeb79 commit b9d285d

File tree

2 files changed

+49
-1
lines changed

2 files changed

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

0 commit comments

Comments
 (0)