All the documentation I\'ve read on the pthreads mutex states only that a mutex prevents multiple threads from accessing shared memory, but how do you specify in the program
See this code:
bool initialized_array = false;
int some_array[10];
void do_some_initialization() { ... };
int get_array_element(int i)
{
if (!initialized_array) {
do_some_initialization();
initialized_array = true;
}
return some_array[i];
}
As you can see, the variables initialized_array
and some_array
are deeply related, but the relationship exists only in the code that handles them both.
That's how mutexes are associated with shared memory; the association happens because you write code that does so. There is no way to say "this mutex protects that shared object", the programmer has to make sure every time the shared object is accessed by a thread, it also performs the synchronization on the correct mutex.
a mutex prevents multiple threads from accessing shared memory
The above is an incorrect statement. By itself, a mutex does not do that. It lets you build code that prevents multiple threads from accessing shared memory or other resources concurrently, but it does not lock anything by itself.
You can build a program that uses a mutex to prevent multiple threads from executing specific pieces of code concurrently. If these pieces of code happen to be accessing a shared memory region, and no other code would try accessing that region concurrently without locking the mutex, then the effect would be that of "locking" that region of memory.
When locked, a mutex prevents any other thread from obtaining a lock on that same mutex. So, if you want some particular data to be thread safe (i.e., a critical section of memory), you should obtain a lock on the mutex before reading from it or writing to it, then release the lock when finished. The mutex itself does not refer to any particular section of memory. It is simply a tool you can use.
Strictly speaking, a mutex only locks/unlocks itself. What shared resources it protects depends entirely on how you use it. You use mutexes (or more generally any synchronisation primitives) to establish a protocol for yourself to safely access shared resources such as data.
For example, you have an array double d[10]
which is to be access by different threads. To protect this from concurrent modifications, you can create one mutex, let's say mutex_for_d
, and program your code so that every time any code accesses d
, it will lock mutex_for_d
first. That way, access to d
is protected by the mutex.
Alternatively, you could decide that each element of the array will be synchronised separately - and have an array of mutexes, always locking the one for the element you access.
This is purely your own protocol and you must make sure you stick to it - if you forget to lock the mutex in one function which modifies d
, the program will still run, but can potentially introduce a data race. For this reason, you will normally want to hide shared data behind a class interface which will guarantee correct locking, something like this:
struct SharedArray
{
double get(size_t idx) const { std::lock_guard<std::mutex> lock(mut); return d[idx]; }
void set(size_t idx, double v) { std::lock_guard<std::mutex> lock(mut); d[idx] = v; }
private:
double d[10];
std::mutex mut;
};
A mutex doesn't lock memory, it "locks" a part of the execution path, and synchronizes memory (but when locking and when unlocking). What is guaranteed is that if one thread holds the mutex, no other threads can acquire it, and any thread attempting to acquire it will be blocked until it is released.
It is also guaranteed that any memory accesses (read or write) will be ordered with respect to acquiring or releasing the mutex; in other words, that any reads made while the mutex is held will reflect any changes made before the mutex was acquired (including those made in a different thread), and that any modifications made while the mutex is held will be potentially visible to all other threads at the latest when the mutex is released. (The other threads will, of course, have to ensure that their memory reads see up to date values for this to work.)
For more information, you really should read Programming with POSIX Threads, by David Butenhof. It's the reference, and explains them in detail.
A mutex locks itself and that's all it locks. It does not lock other memory, it does not lock code. You can design your code so that something other than just the mutex is protected but that's down to how you design the code, not a feature of the mutex itself.
You can tell it doesn't lock data memory because you can freely modify the memory when the mutex is locked by someone else just by not using the mutex:
thread1: thread2:
lock mtx set i to 4 // i is not protected here
set i to 7
unlock mtx
To say it locks code is also not quite right since you can run all sort of different sections of code under the control of a single mutex.
And, if someone manages to get to the code within a mutex block without first claiming the mutex, it can freely run the code even when someone else has the mutex locked:
threadN:
if thread_id == 2: goto skip_label1
lock mtx
:skip1_label1
set i to 7 // not as protected as you think
if thread_id == 2: goto skip_label2
unlock mtx
:skip1_label2