Figure 7.3 shows the operation of a read/write lock being used to synchronize three threads, called thread 1. thread 2, and thread 3. The figure is a sort of timing diagram, with time increasing from left to right. Each of the lines beginning at the labels in the upper left designates the behavior of a specific thread — solid for thread 1, dotted for thread 2, and dashed for thread 3. When the lines drop within the rounded rectangle, they are interacting with the read/write lock. If the
FIGURE 7.3
line drops below the center Line, it shows that the thread has the read/write lock locked, either for exclusive write or for shared read. Lines that hover above the center line represent threads waiting for the lock.
In this example, thread 1 locks the read/write lock for exclusive write. Thread 2 tries to lock the read/write lock for shared read and, finding it already locked for exclusive write, blocks. When thread 1 releases the lock, it awakens thread 2, which then succeeds in locking the read/write lock for shared read. Thread 3 then tries to lock the read/write lock for shared read and, because the read/write lock is already locked for shared read, it succeeds immediately. Thread 1 then tries to lock the read/write lock again for exclusive write access, and blocks because the read/write lock is already locked for read access. When thread 3 unlocks the read/write lock, it cannot awaken thread 1, because there is another reader. Only when thread 2 also unlocks the read/write lock, and the lock becomes unlocked, can thread 1 be awakened to lock the read/write lock for exclusive write access.
The header file rwlock.h and the C source file rwlock.c demonstrate an implementation of read/write locks using standard Pthreads mutexes and condition variables. This is a portable implementation that is relatively easy to understand. One could, of course, create a much more efficient implementation for any specific system based on knowledge of nonportable hardware and operating system characteristics.
The rest of this section shows the details of a read/write lock package. First, rwlock.h describes the interfaces, and then rwlock.c provides the implementation. Part 1 shows the structure of a read/write lock, represented by the type rwlock_t
.
7-9 Of course, there's a mutex to serialize access to the structure. We'll use two separate condition variables, one to wait for read access (called read) and one to wait for write access (called, surprisingly, write), 10 The rwlock_t
structure has a valid member to easily detect common usage errors, such as trying to lock a read/write lock that hasn't been initialized. The member is set to a magic number when the read/write lock is initialized, just as in barrier_init
.
11-12 To enable us to determine whether either condition variable has waiters, we'll keep a count of active readers (r_active) and a flag to indicate an active writer (w_active).
13-14 We also keep a count of the number of threads waiting for read access (r_wait) and for write access (w_wait).
17 Finally, we need a "magic number" for our valid member. (See the footnote in Section 7.1.1 if you missed this part of the barrier example.)
■ rwlock.h part 1 rwlock_t
1 #include
2
3 /*
4 * Structure describing a read/write lock.
5 */
6 typedef struct rwlock_tag {
7 pthread_mutex_t mutex;
8 pthread_cond_t read;
9 pthread_cond_t write;
10 int valid;
11 int r_active;
12 int w_active;
13 int r_wait;
14 int w_wait;
15 } rwlock_t;
16
17 #define RWLOCK VALID 0xfacade
/* wait for read */
/* wait for write */
/* set when valid */
/* readers active */
/* writer active */
/* readers waiting */
/* writers waiting */
We could have saved some space and simplified the code by using a single condition variable, with readers and writers waiting using separate predicate expressions. We will use one condition variable for each predicate, because it is more efficient. This is a common trade-off. The main consideration is that when two predicates share a condition variable, you must always wake them using pthread_cond_broadcast
, which would mean waking all waiters each time the read/write lock is unlocked.