There are various task executors, with different properties, and some of them only support non-blocking calls. So, I was thinking, whether there's a need to use mutex/channel to safely deliver task results to calling go-routine, or whether is it enough simple WaitGroup
?
For sake of simplicity, and specificity of the question, an example using very naive task executor launching function directly as go routine:
func TestRace(t *testing.T) {
var wg sync.WaitGroup
a, b := 1, 2
wg.Add(1)
// this func would be passed to real executor
go func() {
a, b = a+1, b+1
wg.Done()
}()
wg.Wait()
assert.Equal(t, a, 2)
assert.Equal(t, b, 3)
}
Execution of the test above with -race
option didn't fail, on my machine. However, is that enough guarantee? What if go-routine is executed on different CPU core, or on CPU core block (AMD CCX), or on different CPU in multi-socket setups?
So, the question is, can I use WaitGroup
to provide synchronization (block and return values) for non-blocking executors?
JimB should perhaps provide this as the answer, but I'll copy it from his comments, starting with this one:
The
WaitGroup
here is to ensure thata, b = a+1, b+1
has executed, so there's no reason to assume it hasn't.
[and]
[T]he guarantees you have are laid out by the go memory model, which is well documented [here]. [Specifically, the combination of
wg.Done()
andwg.Wait()
in the example suffices to guarantee non-racy access to the two variablesa
andb
.]
As long as this question exists, it's probably a good idea to copy Adrian's comment too:
As @JimB noted, if a value is shared between goroutines, it cannot be stack-allocated, so the question is moot (see How are Go closures layed out in memory?). WaitGroup works correctly.
The fact that closure variables are heap-allocated is an implementation detail: it might not be true in the future. But the sync.WaitGroup
guarantee will still be true in the future, even if some clever future Go compiler is able to keep those variables on some stack.
("Which stack?" is another question entirely, but one for the hypothetical future clever Go compiler to answer. The WaitGroup
and memory model provide the rules.)