Atomic functions or methods?
Channels and mutexes cover most of your code's synchronization needs. You'd rarely have to reach for the sync/atomic
package, but if you have to, you may notice the recommendation that comes with each package-level atomic function such as AddInt32
:
“Consider using the more ergonomic and less error-prone Int32.Add instead.”
Uh, but why? Both the package-level function and the atomic type's method do exactly the same, right? Right, but they do it in different ways. The documentation says “more ergonomic and less error-prone”. Sounds good, but what does that mean?
Atomic types and their methods are more ergonomic
The case for ergonomics is clear to see if you compare the two approaches directly:
// Package-level function (less ergonomic)
var counter int32
atomic.AddInt32(&counter, 1)
// Type-specific method (more ergonomic)
var counter atomic.Int32
counter.Add(1)
The method is, very obviously, shorter to type, hence less wordy. For the package-level function, you have to repeat the counter's type in the function name and have to remember that the counter parameter is a pointer that you must dereference. The method, on the other hand, exposes a clear and brief Add()
method.
Atomic types and their methods are less error-prone
This one is less obvious than the ergonomics, but if you look closely, you notice that the “atomic” counter in the package-level function example isn't atomic at all; it's a plain int32
. Imagine a new developer on the team who fixes a critical bug in a hurry and increments the counter through counter++
. This increment operator isn't atomic, and now your code has a data race condition.
This cannot happen with atomic types like atomic.Int32
, as the increment operator isn't defined on them. The only way to increment an atomic Int32
is by calling Add(1)
.
var counter atomic.Int32
// This won't even compile
counter++
// This works
counter.Add(1)
So: Use the atomic types and their methods instead of the package-level functions. It's not just a matter of taste.