1
1
package sync
2
2
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.
4
18
5
19
type Cond struct {
6
20
L Locker
7
21
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
17
24
}
18
25
19
26
func NewCond (l Locker ) * Cond {
@@ -24,14 +31,14 @@ func (c *Cond) trySignal() bool {
24
31
// Pop a blocked task off of the stack, and schedule it if applicable.
25
32
t := c .blocked .Pop ()
26
33
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
+ }
35
42
return true
36
43
}
37
44
@@ -40,21 +47,29 @@ func (c *Cond) trySignal() bool {
40
47
}
41
48
42
49
func (c * Cond ) Signal () {
50
+ c .lock .Lock ()
43
51
c .trySignal ()
52
+ c .lock .Unlock ()
44
53
}
45
54
46
55
func (c * Cond ) Broadcast () {
47
56
// Signal everything.
57
+ c .lock .Lock ()
48
58
for c .trySignal () {
49
59
}
60
+ c .lock .Unlock ()
50
61
}
51
62
52
63
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 ()
58
73
59
74
// Temporarily unlock L.
60
75
c .L .Unlock ()
@@ -63,22 +78,14 @@ func (c *Cond) Wait() {
63
78
defer c .L .Lock ()
64
79
65
80
// 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).
67
84
return
68
85
}
69
86
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.
83
90
task .Pause ()
84
91
}
0 commit comments