C++11 multithreading locks and atomic primitives
(A question leading to two questions)
In C++11 are locks atomic, in that one can be definitely assured two threads cannot acquire the lock at the same time?
If the answer to the above is "yes", then what is the purpose of double-checked locking?
Finally, why do we need std::lock() when we could just use an atomic primitive (which, obviously- we know is atomic ) and set this to 1 or 0 depending on whether a lock has been acquired or not?
The confused idea behind double-checked locking is to not acquire a lock when checking for the condition and only acquire the lock in the unlikely condition. To get this to work, however, quite a bit of synchronization is still needed which is rather easy to get wrong. Since the primary use of double-checked locking is initialization of singletons (which are bad idea by themselves) and constant objects shared across threads, C++11 actually implements thread safe initialization of function local static
objects. This should remove the majority of the cases where people attempt to get double-checked locking right.
Other than that: the point of mutex locks is that at most one thread can acquire a mutex lock, ie, it is guaranteed that no two threads can acquire the same lock. With respect to using an atomic variable to indicate something similar to a lock, you need to be aware that locking/unlocking a mutex adds additional synchronization beyond what is done by changing an atomic value: it is not enough to know that only one thread is modifying shared state, it is also necessary to signal the changes being made to the system. Also, normal lock may suspend thread execution when the a lock cannot be acquired (although I don't think the implementation is required to do, ie, I think it can do a busy wait using a spin lock).
Locks are 100% atomic unless you try to do something clever like destroy one while someone is acquiring it.
Locking costs time. In cases where you only have to check the lock a fraction of the time, double-checked locking can let you avoid the lock cost, and only have to lock when you can't prove it is safe to skip the lock.
You can't simply replace a lock with an atomic primitive because you can wait on a lock. If you wait on a lock, the OS stops running that thread, and spends its CPU power elsewhere. If you sit looping on an atomic primitive, you keep the CPU busy and not doing useful things.
That being said, there is a lock structure built that way, called a spinlock, that is very fast if you can expect it to only lock for a few cycles.
Also, you can't use condition_variables with an atomic variable, you need a real lock
Yes, C++11 locks are atomic: only one thread can own a lock on a single std::mutex
at a time. That is the whole point of a mutex: to provide mutual exclusion.
The purpose of double-checked locking is to avoid the overhead of acquiring a lock when it is not needed: in particular to avoid the mutual exclusion and consequent serialization when multiple threads run the same bit of code concurrently and the mutual exclusion is no longer needed.
This is typically used around some form of one-time initialization, and still requires synchronization. This synchronization can be done with atomics, but is hard to get right. I do mention how to do it in a blog post I wrote (Lazy Initialization and Double Checked Locking with Atomics) but you're often better off just using local static
objects, or std::call_once
.
Mutexes can be replaced with an atomic flag in some cases, but getting the synchronization right is hard: usually the flag is just some indication that other data can be accessed, and you need to ensure that this data is correctly synchronized. Unless profiling suggests that the mutex is a bottleneck, you are better to stick to a mutex than trying to roll your own synchronization with atomics.
Finally, the main point of std::lock()
is to lock more than one mutex at a time without deadlock. Normally you have to lock mutexes one at a time. However, if thread 1 locks mutex A then mutex B, and thread 2 locks mutex B then mutex A you can get deadlock. std::lock()
avoids this by waiting until the thread can acquire both mutexes, without preventing other threads from locking them in the mean time.
上一篇: 了解std :: atomic :: compare
下一篇: C ++ 11多线程锁和原子基元