36-39 As in barrier_destroy, we destroy all Pthreads synchronization objects, and store each status return. If any of the destruction calls fails, returning a nonzero value, rwl_destroy will return that status, and if they all succeed it will return 0 for success.
¦ rwlock.c part 2 rwl_destroy
1 /*
2 * Destroy a read/write lock.
3 */
4 int rwl_destroy (rwlock_t *rwl)
5 {
6 int status, status1, 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
14 /*
15 * Check whether any threads own the lock; report 'BUSY' if
16 * so.
17 */
18 if (rwl->r_active > 0 || rwl->w_active) {
19 pthread_mutex_unlock (&rwl->mutex);
20 return EBUSY;
21 } 22
23 /*
24 * Check whether any threads are known to be waiting; report
25 * EBUSY if so.
26 */
27 if (rwl->r_wait != 0 || rwl->w_wait != 0) {
28 pthread_mutex_unlock (&rwl->mutex);
29 return EBUSY;
30 } 31
32 rwl->valid = 0;
33 status = pthread_mutex_unlock (&rwl->mutex);
34 if (status != 0)
35 return status;
36 status = pthread_mutex_destroy (&rwl->mutex);
37 status1 = pthread_cond_destroy (&rwl->read);
38 status2 = pthread_cond_destroy (&rwl->write);
39 return (status == 0 ? status
40 : (status1 == 0 ? status1 : status2));
41 }
Part 3 shows the code for rwl_readcleanup and rwl_writecleanup, two cancellation cleanup handlers used in locking the read/write lock for read and write access, respectively. As you may infer from this, read/write locks, unlike barriers, are cancellation points. When a wait is canceled, the waiter needs to decrease the count of threads waiting for either a read or write lock, as appropriate, and unlock the mutex.
¦ rwlock.c part 3 cleanuphandlers
1 /*
2 * Handle cleanup when the read lock condition variable
3 * wait is canceled.
4 *
5 * Simply record that the thread is no longer waiting,
6 * and unlock the mutex.
7 */
8 static void rwl_readcleanup (void *arg)
9 {
10 rwlock_t *rwl = (rwlock_t *)arg;
11
12 rwl->r_wait--;
13 pthread_mutex_unlock (&rwl->mutex);
14 }
15
16 /*
17 * Handle cleanup when the write lock condition variable
18 * wait is canceled.
19 *
20 * Simply record that the thread is no longer waiting,
21 * and unlock the mutex.
22 */
23 static void rwl_writecleanup (void *arg)
24 {
25 rwlock_t *rwl = (rwlock_t *)arg;
26
27 rwl->w_wait--;
28 pthread_mutex_unlock (&rwl->mutex);
29 }
10-26 Part 4 shows rwl_readlock, which locks a read/write lock for read access. If a writer is currently active (w_active is nonzero), we wait for it to broadcast the read condition variable. The r_wait member counts the number of threads waiting to read. This could be a simple boolean variable, except for one problem— when a waiter is canceled, we need to know whether there are any remaining waiters. Maintaining a count makes this easy, since the cleanup handler only needs to decrease the count.
This is one of the places where the code must be changed to convert our read/ write lock from 'reader preference' to 'writer preference,' should you choose to do that. To implement writer preference, a reader must block while there are waiting writers (w_wait > 0), not merely while there are active writers, as we do here.
15-21 Notice the use of the cleanup handler around the condition wait. Also, notice that we pass the argument 0 to pthread_cleanup_pop so that the cleanup code is called only if the wait is canceled. We need to perform slightly different actions when the wait is not canceled. If the wait is not canceled, we need to increase the count of active readers before unlocking the mutex.
¦ rwlock.c part 4 rwl_readlock
1 /*
2 * Lock a read/write lock for read access.
3 */
4 int rwl_readlock (rwlock_t *rwl)
5 {
6 int status;
7
8 if (rwl->valid != RWLOCK_VALID)
9 return EINVAL;