sync: simplify WaitGroup
A comment in waitgroup.go describes the following scenario as the reason to have dynamically created semaphores: // G1: Add(1) // G1: go G2() // G1: Wait() // Context switch after Unlock() and before Semacquire(). // G2: Done() // Release semaphore: sema == 1, waiters == 0. G1 doesn't run yet. // G3: Wait() // Finds counter == 0, waiters == 0, doesn't block. // G3: Add(1) // Makes counter == 1, waiters == 0. // G3: go G4() // G3: Wait() // G1 still hasn't run, G3 finds sema == 1, unblocked! Bug. However, the scenario is incorrect: G3: Add(1) happens concurrently with G1: Wait(), and so there is no reasonable behavior of the program (G1: Wait() may or may not wait for G3: Add(1) which can't be the intended behavior). With this conclusion we can: 1. Remove dynamic allocation of semaphores. 2. Remove the mutex entirely and instead pack counter and waiters into single uint64. This makes the logic significantly simpler, both Add and Wait do only a single atomic RMW to update the state. benchmark old ns/op new ns/op delta BenchmarkWaitGroupUncontended 30.6 32.7 +6.86% BenchmarkWaitGroupActuallyWait 722 595 -17.59% BenchmarkWaitGroupActuallyWait-2 396 319 -19.44% BenchmarkWaitGroupActuallyWait-4 224 183 -18.30% BenchmarkWaitGroupActuallyWait-8 134 106 -20.90% benchmark old allocs new allocs delta BenchmarkWaitGroupActuallyWait 2 1 -50.00% benchmark old bytes new bytes delta BenchmarkWaitGroupActuallyWait 48 16 -66.67% Change-Id: I28911f3243aa16544e99ac8f1f5af31944c7ea3a Reviewed-on: https://go-review.googlesource.com/4117 Run-TryBot: Dmitry Vyukov <dvyukov@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
Showing
Please register or sign in to comment