The POSIX standard allows the programmer to avoid data races via ``POSIX locking''. POSIX locking features a number of primitives, the most fundamental of which are pthread_mutex_lock() and pthread_mutex_unlock(). These primitives operate on locks, which are of type pthread_mutex_t. These locks may be declared statically and initialized with PTHREAD_MUTEX_INITIALIZER, or they may be allocated dynamically and initialized using the pthread_mutex_init() primitive. The demonstration code in this section will take the former course.
The pthread_mutex_lock() primitive ``acquires'' the specified lock, and the pthread_mutex_unlock() ``releases'' the specified lock. Because these are ``exclusive'' locking primitives, only one thread at a time may ``hold'' a given lock at a given time. For example, if a pair of threads attempt to acquire the same lock concurrently, one of the pair will be ``granted'' the lock first, and the other will wait until the first thread releases the lock.
Quick Quiz 5.8: What if I want several threads to hold the same lock at the same time? End Quick Quiz
This exclusive-locking property is demonstrated using the code shown in
Figure
(lock.c).
Line 1 defines and initializes a POSIX lock named lock_a, while
line 2 similarly defines and initializes a lock named lock_b.
Line 3 defines and initializes a shared variable x.
Lines 5-28 defines a function lock_reader() which repeatedly reads the shared variable x while holding the lock specified by arg. Line 10 casts arg to a pointer to a pthread_mutex_t, as required by the pthread_mutex_lock() and pthread_mutex_unlock() primitives.
Quick Quiz 5.9:
Why not simply make the argument to lock_reader()
on line 5 of
Figure
be a pointer to a pthread_mutex_t?
End Quick Quiz
Lines 12-15 acquire the specified pthread_mutex_t, checking for errors and exiting the program if any occur. Lines 16-23 repeatedly check the value of x, printing the new value each time that it changes. Line 22 sleeps for one millisecond, which allows this demonstration to run nicely on a uniprocessor machine. Line 24-27 release the pthread_mutex_t, again checking for errors and exiting the program is any occur. Finally, line 28 returns NULL, again to match the function type required by pthread_create().
Quick Quiz 5.10: Writing four lines of code for each acquisition and release of a pthread_mutex_t sure seems painful! Isn't there a better way? End Quick Quiz
Lines 31-49 of
Figure
shows lock_writer(), which
periodically update the shared variable x while holding the
specified pthread_mutex_t.
As with lock_reader(), line 34 casts arg to a pointer
to pthread_mutex_t, lines 36-39 acquires the specified lock,
and lines 44-47 releases it.
While holding the lock, lines 40-48 increment the shared variable x,
sleeping for five milliseconds between each increment.
Figure
shows a code fragment that runs lock_reader() and
lock_writer() as thread using the same lock, namely, lock_a.
Lines 2-6 create a thread running lock_reader(), and then
Lines 7-11 create a thread running lock_writer().
Lines 12-19 wait for both threads to complete.
The output of this code fragment is as follows:
Creating two threads using same lock: lock_reader(): x = 0 |
Because both threads are using the same lock, the lock_reader() thread cannot see any of the intermediate values of x produced by lock_writer() while holding the lock.
Quick Quiz 5.11:
Is ``x = 0'' the only possible output from the code fragment
shown in
Figure ?
If so, why?
If not, what other output could appear, and why?
End Quick Quiz
Figure
shows a similar code fragment, but this time using different locks:
lock_a for lock_reader() and lock_b for
lock_writer().
The output of this code fragment is as follows:
Creating two threads w/different locks: lock_reader(): x = 0 lock_reader(): x = 1 lock_reader(): x = 2 lock_reader(): x = 3 |
Because the two threads are using different locks, they do not exclude each other, and can run concurrently. The lock_reader() function can therefore see the intermediate values of x stored by lock_writer().
Quick Quiz 5.12: Using different locks could cause quite a bit of confusion, what with threads seeing each others' intermediate states. So should well-written parallel programs restrict themselves to using a single lock in order to avoid this kind of confusion? End Quick Quiz
Quick Quiz 5.13:
In the code shown in
Figure ,
is lock_reader() guaranteed to see all the values produced
by lock_writer()?
Why or why not?
End Quick Quiz
Quick Quiz 5.14:
Wait a minute here!!!
Figure
didn't initialize shared variable x,
so why does it need to be initialized in
Figure
?
End Quick Quiz
Although there is quite a bit more to POSIX exclusive locking, these primitives provide a good start and are in fact sufficient in a great many situations. The next section takes a brief look at POSIX reader-writer locking.
Paul E. McKenney 2011-12-16