// This example uses AfterFunc to define a function which waits on a sync.Cond,
// stopping the wait when a context is canceled.
func ExampleAfterFunc_cond() {
- waitOnCond := func(ctx context.Context, cond *sync.Cond) error {
- stopf := context.AfterFunc(ctx, cond.Broadcast)
+ waitOnCond := func(ctx context.Context, cond *sync.Cond, conditionMet func() bool) error {
+ stopf := context.AfterFunc(ctx, func() {
+ // We need to acquire cond.L here to be sure that the Broadcast
+ // below won't occur before the call to Wait, which would result
+ // in a missed signal (and deadlock).
+ cond.L.Lock()
+ defer cond.L.Unlock()
+
+ // If multiple goroutines are waiting on cond simultaneously,
+ // we need to make sure we wake up exactly this one.
+ // That means that we need to Broadcast to all of the goroutines,
+ // which will wake them all up.
+ //
+ // If there are N concurrent calls to waitOnCond, each of the goroutines
+ // will spuriously wake up O(N) other goroutines that aren't ready yet,
+ // so this will cause the overall CPU cost to be O(N²).
+ cond.Broadcast()
+ })
defer stopf()
- cond.Wait()
- return ctx.Err()
+
+ // Since the wakeups are using Broadcast instead of Signal, this call to
+ // Wait may unblock due to some other goroutine's context becoming done,
+ // so to be sure that ctx is actually done we need to check it in a loop.
+ for !conditionMet() {
+ cond.Wait()
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ }
+
+ return nil
}
- ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
- defer cancel()
+ cond := sync.NewCond(new(sync.Mutex))
+
+ var wg sync.WaitGroup
+ for i := 0; i < 4; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
- var mu sync.Mutex
- cond := sync.NewCond(&mu)
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
+ defer cancel()
- mu.Lock()
- err := waitOnCond(ctx, cond)
- fmt.Println(err)
+ cond.L.Lock()
+ defer cond.L.Unlock()
+
+ err := waitOnCond(ctx, cond, func() bool { return false })
+ fmt.Println(err)
+ }()
+ }
+ wg.Wait()
// Output:
// context deadline exceeded
+ // context deadline exceeded
+ // context deadline exceeded
+ // context deadline exceeded
}
// This example uses AfterFunc to define a function which reads from a net.Conn,