14-15 If there are no more active readers, and at least one thread is waiting for write access, signal the write condition variable to unblock one. Note that there is a race here, and whether you should be concerned about it depends on your notion of what should happen. If another thread that is interested in read access calls rwl_readlock
or rwl_tryreadlock
before the awakened writer can run, the reader may "win," despite the fact that we just selected a writer.
Because our version of read/write locks has "reader preference," this is what we usually want to happen — the writer will determine that it has failed and will resume waiting. (It received a spurious wakeup.) If the implementation changes to prefer writers, the spurious wakeup will not occur, because the potential reader would have to block. The waiter we just unblocked cannot decrease w_wait until it actually claims the lock.
■ rwlock.c part 6 rwl_readunlock
1 /*
2 * Unlock a read/write lock from read access.
3 */
4 int rwl_readunlock (rwlock_t *rwl)
5 {
6 int status, status2;
7
8 if (rwl->valid != RWLOCK_VALID)
9 return EINVAL;
10 status = pthread_mutex_lock (&rwl->mutex);
11 if (status != 0)
12 return status;
13 rwl->r_active--;
14 if (rwl->r_active == 0 && rwl->w_wait > 0)
15 status = pthread_cond_signal (&rwl->write);
16 status2 = pthread_mutex_unlock (&rwl->mutex);
17 return (status2 == 0 ? status : status2);
18 }
13 Part 7 shows rwl_writelock
. This function is much like rwl_readlock
, except for the predicate condition on the condition variable wait. In part 1, I explained that, to convert from "preferred read" to "preferred write," a potential reader would have to wait until there were no active or waiting writers, whereas currently it waits only for active writers. The predicate in rwl_writelock is the converse of that condition. Because we support "preferred read," in theory, we must wait here if there are any active or waiting readers. In fact, it is a bit simpler, because if there are any active readers, there cannot be any waiting readers—the whole point of a read/write lock is that multiple threads can have read access at the same time. On the other hand, we do have to wait if there are any active writers, because we allow only one writer at a time.
25 Unlike r_active, which is a counter, w_active is treated as a boolean. Or is it a counter? There's really no semantic difference, since the value of 1 can be considered a boolean TRUE or a count of 1 — there can be only one active writer at any time.
■ rwlock.c_part 7 rwl_writelock
1 /*
2 * Lock a read/write lock for write access.
3 */
4 int rwl_writelock (rwlock_t *rwl)
5 {
6 int status;
7
8 if (rwl->valid != RWLOCK_VALID)
9 return EINVAL;
10 status = pthread_mutex_lock (&rwl->mutex);
11 if (status != 0)
12 return status;
13 if (rwl->w_active || rwl->r_active > 0) {
14 rwl->w_wait++;
15 pthread_cleanup_push (rwl_writecleanup, (void*)rwl);
16 while (rwl->w_active || rwl->r_active > 0) {
17 status = pthread_cond_wait (&rwl->write, &rwl->mutex);
18 if (status != 0)
19 break;
20 }
21 pthread_cleanup_pop (0);
22 rwl->w_wait--;
23 }
24 if (status == 0)
25 rwl->w_active = 1;
26 pthread_mutex_unlock (&rwl->mutex);
27 return status;
28 }
Part 8 shows rwl_writetrylock. This function is much like rwl_writelock, except that it returns EBUSY if the read/write lock is currently in use (either by a reader or by a writer) rather than waiting for it to become free.
■ rwlock.c part 8 rwl_writetrylock
1 /*
2 * Attempt to lock a read/write lock for write access. Don't
3 * block if unavailable.
4 */
5 int rwl_writetrylock (rwlock_t *rwl)
6 {
7 int status, status2;
8