0

The go memory model document says

To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

And the sync package says

Package sync provides basic synchronization primitives such as mutual exclusion locks

So from this we can conclude that sync.Mutex is a synchronization primitive. There's also a very strong hint that other types in that package are synchronization primitives. However, it doesn't say explicitly that e.g. sync.WaitGroup is.

Reading the source of WaitGroup, I can't convince myself completely that memory operations won't be rearranged around WaitGroup functions (the same way I could with java's synchronized keyword for example). I believe it is serializing before/after, but how can I be sure.

Is sync.WaitGroup a "synchronization primitive"? I'm not simply looking for the answer "yes" (or "no" for that matter), but pointers that can prove the case.

Johannes Hoff
  • 3,731
  • 5
  • 31
  • 37
  • 1
    WaitGroup wouldn't be very useful if it wasn't a synchronization primitive. – JimB Dec 11 '17 at 21:32
  • It also wouldn't be in the `sync` package. – Adrian Dec 11 '17 at 21:33
  • Thanks for the comments. However, it doesn't really answer my question other than on an "intuitive" level. What is it about this code or the documentation that guarantees memory access won't be rearranged before/after the `WaitGroup` access? – Johannes Hoff Dec 11 '17 at 21:41

2 Answers2

3

If you go against wise advice and follow turtles all the way down...

wg.Add() leads to line 63 which leads into atomic.AddUint64() which leads to this assembly code:

LOCK
XADDQ   AX, 0(BP)

On the other hand wg.Wait() leads to line 121 which leads to atomic.CompareAndSwapUint64() which leads to:

LOCK
CMPXCHGQ    CX, 0(BP)

And that is apparently how you build a WaitGroup :). With locked atomic exchange and add and locked atomic compare and exchange. Pretty conclusive to me. You cannot fight the assembler. OK maybe you can I cannot.

On x86 locks

k1m190r
  • 1,213
  • 15
  • 26
2

sync.WaitGroup is synchronized. If you read the source, you'll see it uses sync/atomic to synchronize operations on the counters.

Adrian
  • 42,911
  • 6
  • 107
  • 99
  • Where is it guaranteed that the compiler doesn't move memory access instructions before or after the `WaitGroup` functions? – Johannes Hoff Dec 11 '17 at 21:37
  • What do you mean? `WaitGroup` is internally synchronized, any code outside those functions is your responsibility. – Adrian Dec 11 '17 at 21:39
  • 1
    https://golang.org/src/sync/waitgroup.go?s=3482:3518#L111. The package uses atomic loads and saves, which the runtime guarantees are synchronized. If you call `.Wait()`, memory operations that occur after that _cannot_ be rearranged before the atomic load operation (and therefore before the `Wait()` call). – Kaedys Dec 11 '17 at 21:41
  • Oh I understand, a guarantee that it won't move a memory op after the call to before the call. I believe @Kaedys is right here - the `sync/atomic` calls act as a memory barrier. If it did not, `WaitGroup` would be useless (which it isn't). – Adrian Dec 11 '17 at 21:44
  • @Adrian: I mean, the compiler is free to move instructions around as it pleases. Where is that guarantee codified? (edit: ok you got it while I was updating the answer) – Johannes Hoff Dec 11 '17 at 21:44
  • 2
    https://golang.org/ref/mem#tmp_2 `Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program.`. That means that a memory operation that occurs *after* the atomic read memory operation MUST always function as if it occurred after that memory operation within the scope of a single goroutine (which is all the guarantee that a Waitgroup, or any synchronization primitive, makes anyway). The compiler can move the operations around, but not enough to violate that constraint. – Kaedys Dec 11 '17 at 21:49
  • 2
    Kaedys beat me to it while I was searching the spec. Also note the "Advice" section at the same page Kaedys linked: "If you must read the rest of this document to understand the behavior of your program, you are being too clever. Don't be clever." The `sync` and `sync/atomic` packages work as advertised. If you can produce a scenario in which they don't, file a bug report. If you're really keen on learning exactly how it works behind-the-scenes, read the complete source of the compiler, `sync`, and `sync/atomic`. – Adrian Dec 11 '17 at 21:56