Are atomic variables lock-free?

2020-02-18 04:05发布

问题:

When we talk about atomic variables, such as C++11's atomic<>, is it lock free? Or is lock-freeness something different? If I manage a queue with atomic variables, will it be slower than a lock-free queue?

回答1:

The standard does not specify if atomic objects are lock-free. On a platform that doesn't provide lock-free atomic operations for a type T, atomic<T> objects may be implemented using a mutex, which wouldn't be lock-free. In that case, any containers using these objects in their implementation would not be lock-free either.

The standard does provide a way to check if an atomic<T> variable is lock-free: you can use var.is_lock_free() or atomic_is_lock_free(&var). These functions are guaranteed to always return the same value for the same type T on a given program execution. For basic types such as int, There are also macros provided (e.g. ATOMIC_INT_LOCK_FREE) which specify if lock-free atomic access to that type is available.



回答2:

Lock-free usually applies to data structures shared between multiple threads, where the synchronisation mechanism is not mutual exclusion; the intention is that all threads should keep making some kind of progress instead of sleeping on a mutex.

atomic<T> variables don't use locks (at least where T is natively atomic on your platform), but they're not lock-free in the sense above. You might use them in the implementation of a lock-free container, but they're not sufficient on their own.

Eg, atomic<queue<T>> wouldn't suddenly make a normal std::queue into a lock-free data structure. You could however implement a genuinely lock-free atomic_queue<T> whose members were atomic.

Note that even if atomic<int> is natively atomic and not emulated with a lock on your platform, that does not make it lock-free in any interesting way. Plain int is already lock-free in this sense: the atomic<> wrapper gets you explicit control of memory ordering, and access to hardware synchronisation primitives.



回答3:

Marketting and cool factor aside, it does not make a twopenny toss of a difference whether the magic C++ syntactic (brown) sugar ends up implementing a direct bus lock or a mutex (which might rely on bus locks but, as a commentator noted, takes advantage of OS internals to do it in a more efficient way), or nothing at all if you are unlucky enough to run on a single-procesor architecture.

Mutex are semantically lock free already. They implement all you might dream of in terms of scheduler-niceness, namely priority inversion handling and reentrancy. You cannot deadlock on a mutex (well actually you might if you tried very hard, but as far as guarding a variable is concerned, you won't), and using a mutex will not have any more noticeable side effect on the other processes or the operating system than a bus-locked variable.

The only difference is that a programmer might (by design or by mistake) hold a mutex for an unreasonable amount of time, while an equally incompetent programmer might poll on a "Wait-free" variable and achieve the same silly results, with a catastrophic bus slowdown that will put a lot more stress on the system as a whole than a faulty mutex use (OK, my previous allusion to a BSOD was just a juvenile provokation, though I still suspect some drivers might not react very kindly to heavy bus contention). Anyway, this problem is soon solved when the mutex calls are wrapped around a linear access to a reasonably small amount of memory.

"Lock free" is selling dreams to wanabee hardboiled programmers. I find it quite amusing that a mechanism that indeeds relies on locking the multiprocessor bus might be called that.

What a "Lock free" variable access does is pester the hardware by neutering the bus cache system, forbidding bus access scheduling, and generally disabling all the mechanisms that allow your average multiprocessor bus to do a decent job.
Each time you're accessing a "Lock free" magic variable, you are pelting a handful of sand into the bus controller's cogwheels.

Like any concurrent access mechanism, bus-locked variables (sorry, I meant "Lock free variables") are costly and have potential negative side effects that are extremely difficult to predict or even diagnose.

As long as you're using these new shiny toys as you would mutexes, i.e. very sparsely and only for a few good reasons (and no, acting the super cool Mr wait-free is not one of them), it's fine.

But if you start sprinkling bus locks all over the place, or (God forbids!) polling bus-locked variables as a cheap and easy replacement for proper synchronization objects (what the super cool Mr wait-free might call "brewing your own homemade spinlocks in 3 easy steps"), you will only manage to turn whatever cutting edge hardware your code runs on into a circa 1995 Pentium I emulator.