I'm trying to understand how locks work.
Let's say I want to implement a really simple lock in C++
class Resource{
public:
bool lock();
void unlock();
... methods to change/read the Resource ...
private:
bool isLocked;
}
The user of the Resource calls lock(), and if isLocked is true, then lock() returns false, and the user of the Resource has to either wait, or do something else. If isLocked is false, then lock() sets isLocked to true, and returns true. Then the caller can do whatever he wants to the resource. He calls unlock() on the resource afterwards to set isLocked to false.
However, what if two users of the resource call lock() at precisely the same time? Does this situation rarely happen? I think more formally, this involves making the lock() operation "atomic", though I'm not precisely sure what that word means.
3 Answers 3
With the old, standard C++, you cannot implement your own lock, since the lock variable itself is in a data race.
C++11 and C11 add atomic variables, which you can use for precisely this purpose; e.g. in C++:
#include <atomic>
std::atomic<bool> isLocked;
bool lock() { return !isLocked.exchange(true); }
void unlock() { isLocked = false; }
The key here are the atomic exchange and the (implicit) atomic store, which generate special hardware instructions and are always race-free, and which you cannot "fake" with ordinary variables.
Comments
"Atomic" means that the operation can't be interrupted. That is, you can be sure that the semantics of that operation are the same regardless of the behaviour of other threads/processes. You're right that something in your lock() call will likely have to be atomic. Most architectures provide some helpful instructions with guaranteed atomic behaviour - you might also find some libraries built on those operations to give you more flexibility at the higher layer you're programming at.
2 Comments
lock() method as a whole, or particular statements in the lock() method? The first option seems easier to work with.It's not at all rare. It's called a race condition, and is the cause of many (if not most) bugs in multithreaded code.
The C++ standard doesn't really have any concept of threads/atomicity and so on,1 so you'll need to rely on synchronisation primitives offered by your OS (or perhaps via Boost).
1. This is no longer true with C++11.