Skip to content

Commit f48ccce

Browse files
committed
runtime: add cheap atomic condition variable
1 parent b44c02b commit f48ccce

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

src/runtime/cond.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// +build !scheduler.none
2+
3+
package runtime
4+
5+
import (
6+
"internal/task"
7+
"sync/atomic"
8+
"unsafe"
9+
)
10+
11+
// notifiedPlaceholder is a placeholder task which is used to indicate that the condition variable has been notified.
12+
var notifiedPlaceholder task.Task
13+
14+
// Cond is a simplified condition variable, useful for notifying goroutines of interrupts.
15+
type Cond struct {
16+
t *task.Task
17+
}
18+
19+
// Notify sends a notification.
20+
// If the condition variable already has a pending notification, this returns false.
21+
func (c *Cond) Notify() bool {
22+
for {
23+
t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
24+
switch t {
25+
case nil:
26+
// Nothing is waiting yet.
27+
// Apply the notification placeholder.
28+
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), unsafe.Pointer(&notifiedPlaceholder)) {
29+
return true
30+
}
31+
case &notifiedPlaceholder:
32+
// The condition variable has already been notified.
33+
return false
34+
default:
35+
// Unblock the waiting task.
36+
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
37+
runqueuePushBack(t)
38+
return true
39+
}
40+
}
41+
}
42+
}
43+
44+
// Poll checks for a notification.
45+
// If a notification is found, it is cleared and this returns true.
46+
func (c *Cond) Poll() bool {
47+
for {
48+
t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
49+
switch t {
50+
case nil:
51+
// No notifications are present.
52+
return false
53+
case &notifiedPlaceholder:
54+
// A notification arrived and there is no waiting goroutine.
55+
// Clear the notification and return.
56+
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
57+
return true
58+
}
59+
default:
60+
// A task is blocked on the condition variable, which means it has not been notified.
61+
return false
62+
}
63+
}
64+
}
65+
66+
// Wait for a notification.
67+
// If the condition variable was previously notified, this returns immediately.
68+
func (c *Cond) Wait() {
69+
cur := task.Current()
70+
for {
71+
t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
72+
switch t {
73+
case nil:
74+
// Condition variable has not been notified.
75+
// Block the current task on the condition variable.
76+
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), nil, unsafe.Pointer(cur)) {
77+
task.Pause()
78+
return
79+
}
80+
case &notifiedPlaceholder:
81+
// A notification arrived and there is no waiting goroutine.
82+
// Clear the notification and return.
83+
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
84+
return
85+
}
86+
default:
87+
panic("interrupt.Cond: condition variable in use by another goroutine")
88+
}
89+
}
90+
}

src/runtime/cond_nosched.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// +build scheduler.none
2+
3+
package runtime
4+
5+
import "runtime/interrupt"
6+
7+
// Cond is a simplified condition variable, useful for notifying goroutines of interrupts.
8+
type Cond struct {
9+
fired bool
10+
}
11+
12+
// Notify sends a notification.
13+
// If the condition variable already has a pending notification, this returns false.
14+
func (c *Cond) Notify() bool {
15+
i := interrupt.Disable()
16+
prev := c.fired
17+
c.fired = true
18+
interrupt.Restore(i)
19+
return !prev
20+
}
21+
22+
// Poll checks for a notification.
23+
// If a notification is found, it is cleared and this returns true.
24+
func (c *Cond) Poll() bool {
25+
i := interrupt.Disable()
26+
fired := c.fired
27+
if fired {
28+
c.fired = false
29+
}
30+
interrupt.Restore(i)
31+
return fired
32+
}
33+
34+
// Wait for a notification.
35+
// If the condition variable was previously notified, this returns immediately.
36+
func (c *Cond) Wait() {
37+
for !c.Poll() {
38+
}
39+
}

0 commit comments

Comments
 (0)