11package 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
519type 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
1926func 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
4249func (c * Cond ) Signal () {
50+ c .lock .Lock ()
4351 c .trySignal ()
52+ c .lock .Unlock ()
4453}
4554
4655func (c * Cond ) Broadcast () {
4756 // Signal everything.
57+ c .lock .Lock ()
4858 for c .trySignal () {
4959 }
60+ c .lock .Unlock ()
5061}
5162
5263func (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