Is there a difference in Go between a counter using atomic operations and one using a mutex?

后端 未结 3 1459
南方客
南方客 2021-02-07 11:26

I have seen some discussion lately about whether there is a difference between a counter implemented using atomic increment/load, and one using a mutex to synchronise increment/

相关标签:
3条回答
  • 2021-02-07 11:54

    There is no difference in behavior. There is a difference in performance.

    Mutexes are slow, due to the setup and teardown, and due to the fact that they block other goroutines for the duration of the lock.

    Atomic operations are fast because they use an atomic CPU instruction, rather than relying on external locks to.

    Therefore, whenever it is feasible, atomic operations should be preferred.

    0 讨论(0)
  • 2021-02-07 11:57

    Atomics are faster in the common case: the compiler translates each call to a function from the sync/atomic package to a special set of machine instructions which basically operate on the CPU level — for instance, on x86 architectures, an atomic.AddInt64 would be translated to some plain ADD-class instruction prefixed with the LOCK instruction (see this for an example) — with the latter ensuring coherent view of the updated memory location across all the CPUs in the system.

    A mutex is a much complicated thing as it, in the end, wraps some bit of the native OS-specific thread synchronization API (for instance, on Linux, that's futex).

    On the other hand, the Go runtime is pretty much optimized when it comes to synchronization stuff (which is kinda expected — given one of the main selling points of Go), and the mutex implementation tries to avoid hitting the kernel to perform synchronization between goroutines, if possible, and carry it out completely in the Go runtime itself.

    This might explain no noticeable difference in the timings in your benchmarks, provided the contention over the mutexes was reasonably low.


    Still, I feel oblidged to note — just in case — that atomics and higher-level synchronization facilities are designed to solve different tasks. Say, you can't use atomics to protect some memory state during the execution of a whole function — and even a single statement, in the general case.

    0 讨论(0)
  • 2021-02-07 12:15

    Alright, I'm going to attempt to self-answer for some closure. Edits are welcome.

    There is some discussion about the atomic package here. But to quote the most telling comments:

    The very short summary is that if you have to ask, you should probably avoid the package. Or, read the atomic operations chapter of the C++11 standard; if you understand how to use those operations safely in C++, then you are more than capable of using Go's sync/atomic package.

    That said, sticking to atomic.AddInt32 and atomic.LoadInt32 is safe as long as you are just reporting statistical information, and not actually relying on the values carrying any meaning about the state of the different goroutines.

    And:

    What atomicity does not guarantee, is any ordering of observability of values. I mean, atomic.AddInt32() does only guarantee that what this operation stores at &cnt will be exactly *cnt + 1 (with the value of *cnt being what the CPU executing the active goroutine fetched from memory when the operation started); it does not provide any guarantee that if another goroutine will attempt to read this value at the same time it will fetch that same value *cnt + 1.

    On the other hand, mutexes and channels guarantee strict ordering of accesses to values being shared/passed around (subject to the rules of Go memory model).

    In regards to why the code sample in the question never finishes, this is due to fact that the func that is reading the counter is in a very tight loop. When using the atomic counter, there are no syncronisation events (e.g. mutex lock/unlock, syscalls) which means that the goroutine never yields control. The result of this is that this goroutine starves the thread it is running on, and prevents the scheduler from allocating time to any other goroutines allocated to that thread, this includes ones that increment the counter meaning the counter never reaches 10000000.

    0 讨论(0)
提交回复
热议问题