@@ -3,35 +3,65 @@ package sync
3
3
import "internal/task"
4
4
5
5
type WaitGroup struct {
6
- counter uint
7
- waiters task.Stack
6
+ futex task.Futex
8
7
}
9
8
10
9
func (wg * WaitGroup ) Add (delta int ) {
11
- if delta > 0 {
12
- // Check for overflow.
13
- if uint (delta ) > (^ uint (0 ))- wg .counter {
14
- panic ("sync: WaitGroup counter overflowed" )
15
- }
10
+ switch {
11
+ case delta > 0 :
12
+ // Delta is positive.
13
+ for {
14
+ // Check for overflow.
15
+ counter := wg .futex .Load ()
16
+ if uint32 (delta ) > (^ uint32 (0 ))- counter {
17
+ panic ("sync: WaitGroup counter overflowed" )
18
+ }
16
19
17
- // Add to the counter.
18
- wg .counter += uint (delta )
19
- } else {
20
- // Check for underflow.
21
- if uint (- delta ) > wg .counter {
22
- panic ("sync: negative WaitGroup counter" )
20
+ // Add to the counter.
21
+ if wg .futex .CompareAndSwap (counter , counter + uint32 (delta )) {
22
+ // Successfully added.
23
+ return
24
+ }
23
25
}
26
+ default :
27
+ // Delta is negative (or zero).
28
+ for {
29
+ counter := wg .futex .Load ()
24
30
25
- // Subtract from the counter.
26
- wg .counter -= uint (- delta )
31
+ // Check for underflow.
32
+ if uint32 (- delta ) > counter {
33
+ panic ("sync: negative WaitGroup counter" )
34
+ }
35
+
36
+ // Subtract from the counter.
37
+ if ! wg .futex .CompareAndSwap (counter , counter - uint32 (- delta )) {
38
+ // Could not swap, trying again.
39
+ continue
40
+ }
27
41
28
- // If the counter is zero, everything is done and the waiters should be resumed.
29
- // This code assumes that the waiters cannot wake up until after this function returns.
30
- // In the current implementation, this is always correct.
31
- if wg .counter == 0 {
32
- for t := wg .waiters .Pop (); t != nil ; t = wg .waiters .Pop () {
33
- scheduleTask (t )
42
+ // If the counter is zero, everything is done and the waiters should
43
+ // be resumed.
44
+ // When there are multiple thread, there is a chance for the counter
45
+ // to go to zero, WakeAll to be called, and then the counter to be
46
+ // incremented again before a waiting goroutine has a chance to
47
+ // check the new (zero) value. However the last increment is
48
+ // explicitly given in the docs as something that should not be
49
+ // done:
50
+ //
51
+ // > Note that calls with a positive delta that occur when the
52
+ // > counter is zero must happen before a Wait.
53
+ //
54
+ // So we're fine here.
55
+ if counter - uint32 (- delta ) == 0 {
56
+ // TODO: this is not the most efficient implementation possible
57
+ // because we wake up all waiters unconditionally, even if there
58
+ // might be none. Though since the common usage is for this to
59
+ // be called with at least one waiter, it's probably fine.
60
+ wg .futex .WakeAll ()
34
61
}
62
+
63
+ // Successfully swapped (and woken all waiting tasks if needed).
64
+ return
35
65
}
36
66
}
37
67
}
@@ -41,14 +71,15 @@ func (wg *WaitGroup) Done() {
41
71
}
42
72
43
73
func (wg * WaitGroup ) Wait () {
44
- if wg .counter == 0 {
45
- // Everything already finished.
46
- return
47
- }
48
-
49
- // Push the current goroutine onto the waiter stack.
50
- wg .waiters .Push (task .Current ())
74
+ for {
75
+ counter := wg .futex .Load ()
76
+ if counter == 0 {
77
+ return // everything already finished
78
+ }
51
79
52
- // Pause until the waiters are awoken by Add/Done.
53
- task .Pause ()
80
+ if wg .futex .Wait (counter ) {
81
+ // Successfully woken by WakeAll (in wg.Add).
82
+ break
83
+ }
84
+ }
54
85
}
0 commit comments