Reputation: 71
Are all locks implemented using atomic operations? In some book, it says "one lock needs several atomic operations". I thought atomic operations and locks are different things. But it seems I was wrong. What's the relationship between locks and atomic operations?
Upvotes: 4
Views: 3531
Reputation: 470
Locks and atomics Locks are a protection mechanism to perform complex, thread-unsafe operations on some data that is protected by the lock. Atomic operations are operations that can never be observed only part-way through, and are not interrupted if multiple threads are simultaneously accessing the same variables (note that in C++, variables on which atomic operations are performed are called atomic too). Locks are an abstract concept implemented using atomic operations.
How to use mutexes / locks A mutex is the standard way to perform locking: A mutex always guards some variables that are associated with it. It is either locked or unlocked, and only one thread at a time can lock it. While the mutex is locked, the thread can perform thread-unsafe operations on its guarded variables, and it is guaranteed that no other thread can interfere or observe partial changes made by the thread that holds the lock. After the thread is done modifying the variables, it can unlock the mutex, so that other threads can now access the guarded variables (by locking the mutex themselves).
Note that whenever a thread wants to access any of the protected variables in any way, it needs to lock the associated mutex. Otherwise, it is no longer guaranteed that threads won't interfere with each other's work.
How are locks implemented? As far as I know, locks/mutexes are always implemented using atomic operations. One such operation is the atomic exchange: It allows you to read a value from a variable, and write a new value to it, atomically, so no other thread can interfere in this process or observe it half-way done. A mutex would be implemented using an atomic bool (true = LOCKED, false = UNLOCKED), and locking it is performed by performing the following:
while(mutex.locked.exchange(LOCKED) == LOCKED);
This always sets the mutex to LOCKED, and reads the previous value, and if it was UNLOCKED, finishes. In this scenario, we are the only thread that set the mutex from UNLOCKED to LOCKED.
Unlocking is very simple: mutex.locked = UNLOCKED;
This simply sets the mutex to UNLOCKED, and it can then be locked again by other threads.
Note For simplicity, I did leave out memory ordering, an aspect of atomics which deals with the order in which changes made by one thread become visible to other threads (as with some memory orders, changes may be observed in a different order than in which they were made, and multiple threads may even see changes in inconsistent orders). Memory ordering is very complex to get into.
Conclusion As a general rule of thumb, unless you really know what you're doing, you should always use mutexes and locks, and not atomics. Although mutexes are slower, they are complex enough to get into already, and are much easier to use correctly. They automatically deal with visibility of changes made, so your program becomes easier to understand and write.
Upvotes: 4