Skip to content

Commit 6faf36f

Browse files
aykevldeadprogram
authored andcommitted
sync: make Cond MT-safe
This actually simplifies the code and avoids a heap allocation in the call to Wait. Instead, it uses the Data field of the task to store information on whether a task was signalled early.
1 parent edb2f2a commit 6faf36f

File tree

1 file changed

+44
-37
lines changed

1 file changed

+44
-37
lines changed

src/sync/cond.go

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
package sync
22

3-
import "internal/task"
3+
import (
4+
"internal/task"
5+
"unsafe"
6+
)
7+
8+
// Condition variable.
9+
// A goroutine that called Wait() can be in one of a few states depending on the
10+
// Task.Data field:
11+
// - When entering Wait, and before going to sleep, the data field is 0.
12+
// - When the goroutine that calls Wait changes its data value from 0 to 1, it
13+
// is going to sleep. It has not been awoken early.
14+
// - When instead a call to Signal or Broadcast can change the data field from 0
15+
// to 1, it will _not_ go to sleep but be signalled early.
16+
// This can happen when a concurrent call to Signal happens, or the Unlock
17+
// function calls Signal for some reason.
418

519
type Cond struct {
620
L Locker
721

8-
unlocking *earlySignal
9-
blocked task.Stack
10-
}
11-
12-
// earlySignal is a type used to implement a stack for signalling waiters while they are unlocking.
13-
type earlySignal struct {
14-
next *earlySignal
15-
16-
signaled bool
22+
blocked task.Stack
23+
lock task.PMutex
1724
}
1825

1926
func NewCond(l Locker) *Cond {
@@ -24,14 +31,14 @@ func (c *Cond) trySignal() bool {
2431
// Pop a blocked task off of the stack, and schedule it if applicable.
2532
t := c.blocked.Pop()
2633
if t != nil {
27-
scheduleTask(t)
28-
return true
29-
}
30-
31-
// If there any tasks which are currently unlocking, signal one.
32-
if c.unlocking != nil {
33-
c.unlocking.signaled = true
34-
c.unlocking = c.unlocking.next
34+
dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data))
35+
36+
// The data value is 0 when the task is not yet sleeping, and 1 when it is.
37+
if dataPtr.Swap(1) != 0 {
38+
// The value was already 1, so the task went to sleep (or is about to go
39+
// to sleep). Schedule the task to be resumed.
40+
scheduleTask(t)
41+
}
3542
return true
3643
}
3744

@@ -40,21 +47,29 @@ func (c *Cond) trySignal() bool {
4047
}
4148

4249
func (c *Cond) Signal() {
50+
c.lock.Lock()
4351
c.trySignal()
52+
c.lock.Unlock()
4453
}
4554

4655
func (c *Cond) Broadcast() {
4756
// Signal everything.
57+
c.lock.Lock()
4858
for c.trySignal() {
4959
}
60+
c.lock.Unlock()
5061
}
5162

5263
func (c *Cond) Wait() {
53-
// Add an earlySignal frame to the stack so we can be signalled while unlocking.
54-
early := earlySignal{
55-
next: c.unlocking,
56-
}
57-
c.unlocking = &early
64+
// Mark us as not yet signalled or sleeping.
65+
t := task.Current()
66+
dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data))
67+
dataPtr.Store(0)
68+
69+
// Add us to the list of waiting goroutines.
70+
c.lock.Lock()
71+
c.blocked.Push(t)
72+
c.lock.Unlock()
5873

5974
// Temporarily unlock L.
6075
c.L.Unlock()
@@ -63,22 +78,14 @@ func (c *Cond) Wait() {
6378
defer c.L.Lock()
6479

6580
// If we were signaled while unlocking, immediately complete.
66-
if early.signaled {
81+
if dataPtr.Swap(1) != 0 {
82+
// The data value was already 1, so we got a signal already (and weren't
83+
// scheduled because trySignal was the first to change the value).
6784
return
6885
}
6986

70-
// Remove the earlySignal frame.
71-
prev := c.unlocking
72-
for prev != nil && prev.next != &early {
73-
prev = prev.next
74-
}
75-
if prev != nil {
76-
prev.next = early.next
77-
} else {
78-
c.unlocking = early.next
79-
}
80-
81-
// Wait for a signal.
82-
c.blocked.Push(task.Current())
87+
// We were the first to change the value from 0 to 1, meaning we did not get
88+
// a signal during the call to Unlock(). So we wait until we do get a
89+
// signal.
8390
task.Pause()
8491
}

0 commit comments

Comments
 (0)