5.2.3 POSIX Locking
Keywords: perfbook_html

##
5.2.3 POSIX Locking

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

Figure: Demonstration of Exclusive Locks

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: Demonstration of Same Exclusive Lock

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: Demonstration of Different Exclusive Locks

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.