Êíèãà: Programming with POSIX® Threads

7.1.2 Read/write locks

7.1.2 Read/write locks

A read/write lock is a lot like a mutex. It is another way to prevent more than one thread from modifying shared data at the same time. But unlike a mutex it distinguishes between reading data and writing data. A mutex excludes all other threads, while a read/write lock allows more than one thread to read the data, as long as none of them needs to change it.

Read/write locks are used to protect information that you need to read frequently but usually don't need to modify. For example, when you build a cache of recently accessed information, many threads may simultaneously examine the cache without conflict. When a thread needs to update the cache, it must have exclusive access.

When a thread locks a read/write lock, it chooses shared read access or exclusive write access. A thread that wants read access can't continue while any thread currently has write access. A thread trying to gain write access can't continue when another thread currently has either write access or read access.

When both readers and writers are waiting for access at the same time, the readers are given precedence when the write lock is released. Read precedence favors concurrency because it potentially allows many threads to accomplish work simultaneously. Write precedence on the other hand would ensure that pending modifications to the shared data are completed before the data is used. There's no absolute right or wrong policy, and if you don't find the implementation here appropriate for you, it is easy to change.

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 Read/write lock operation

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 <pthread.h>

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.

We keep track of a boolean variable for "writer active," since there can only be one. There are also counters for "readers active," "readers waiting," and "writers waiting." We could get by without counters for readers and writers waiting. All readers are awakened simultaneously using a broadcast, so it doesn't matter how many there are. Writers are awakened only if there are no readers, so we could dispense with keeping track of whether there are any threads waiting to write (at the cost of an occasional wasted condition variable signal when there are no waiters).

We count the number of threads waiting for read access because the condition variable waits might be canceled. Without cancellation, we could use a simple flag — "threads are waiting for read" or "no threads are waiting for read." Each thread could set it before waiting, and we could clear it before broadcasting to wake all waiting readers. However, because we can't count the threads waiting on a condition variable, we wouldn't know whether to clear that flag when a waiting reader was canceled. This information is critical, because if there are no readers waiting when the read/write lock is unlocked, we must wake a writer — but we cannot wake a writer if there are waiting readers. A count of waiting readers, which we can decrease when a waiter is canceled, solves the problem.

The consequences of "getting it wrong" are less important for writers than for readers. Because we check for readers first, we don't really need to know whether there are writers. We could signal a "potential writer" anytime the read/write lock was released with no waiting readers. But counting waiting writers allows us to avoid a condition variable signal when no threads are waiting.

Part 2 shows the rest of the definitions and the function prototypes.

4-6 The RWLOCK_INITIALlZER macro allows you to statically initialize a read/write lock.

11-18 Of course, you must also be able to initialize a read/write lock that you cannot allocate statically, so we provide rwl_init to initialize dynamically, and rwl_ destroy to destroy a read/write lock once you're done with it. In addition, there are functions to lock and unlock the read/write lock for either read or write access. You can "try to lock" a read/write lock, either for read or write access, by calling rwl_readtrylock or rwl_writetrylock., just as you can try to lock a mutex by calling pthread_mutex_trylock.

? rwlock.h part 2 interfaces

1 /*
2 * Support static initialization of barriers.
3 */
4 #define RWL_INITIALIZER
5 {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER,
6 PTHREAD_COND_INITIALIZER, RWLOCK_VALID, 0, 0, 0, 0}
7
8 /*
9 * Define read/write lock functions.
10 */
11 extern int rwl_init (rwlock_t *rwlock);
12 extern int rwl_destroy (rwlock_t *rwlock);
13 extern int rwl_readlock (rwlock_t *rwlock);
14 extern int rwl_readtrylock (rwlock_t *rwlock);
15 extern int rwl_readunlock (rwlock_t *rwlock);
16 extern int rwl_writelock (rwlock_t *rwlock);
17 extern int rwl_writetrylock (rwlock_t *rwlock);
18 extern int rwl_writeunlock (rwlock_t *rwlock);

The file rwlock.c contains the implementation of read/write locks. The following examples break down each of the functions used to implement the rwlock.h interfaces.

Part 1 shows rwl_init, which initializes a read/write lock. It initializes the Pthreads synchronization objects, initializes the counters and flags, and finally sets the valid sentinel to make the read/write lock recognizable to the other interfaces. If we are unable to initialize the read condition variable, we destroy the mutex that we'd already created. Similarly, if we are unable to initialize the write condition variable, we destroy both the mutex and the read condition variable.

? rwlock.c part 1 rwl_init

1 #include <pthread.h>
2 #include "errors.h"
3 #include "rwlock.h"

4
5 /*
6 * Initialize a read/write lock.
7 */
8 int rwl_init (rwlock_t *rwl)
9 {
10 int status;

11
12 rwl->r_active = 0;
13 rwl->r_wait = rwl->w_wait = 0;
14 rwl->w_active = 0;
15 status = pthread_mutex_init (&rwl->mutex, NULL);
16 if (status != 0)
17  return status;
18 status = pthread_cond_init (&rwl->read, NULL);
19 if (status != 0) {
20 /* if unable to create read CV, destroy mutex */
21  pthread_mutex_destroy (&rwl->mutex);
22  return status;
23 }
24 status = pthread_cond_init (&rwl->write, NULL);
25 if (status != 0) {
26 /* if unable to create write CV, destroy read CV and mutex */
27  pthread_cond_destroy (&rwl->read);
28  pthread_mutex_destroy (&rwl->mutex);
29  return status;
30 }
31 rwl->valid = RWLOCK_VALID;
32 return 0;
33 }

Part 2 shows the rwl_destroy function, which destroys a read/write lock.

8-9 We first try to verify that the read/write lock was properly initialized by checking the valid member. This is not a complete protection against incorrect usage, but it is cheap, and it will catch some of the most common errors. See the annotation for barrier.c, part 2, for more about how the valid member is used.

10-30 Check whether the read/write lock is in use. We look for threads that are using or waiting for either read or write access. Using two separate if statements makes the test slightly more readable, though there's no other benefit.

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;
10 status = pthread_mutex_lock (&rwl->mutex);
11 if (status != 0)
12  return status;
13 if (rwl->w_active) {
14  rwl->r_wait++;
15  pthread_cleanup_push (rwl_readcleanup, (void*)rwl);
16  while (rwl->w_active) {
17  status = pthread_cond_wait (&rwl->read, &rwl->mutex);
18  if (status != 0)
19  break;
20  }
21 pthread_cleanup_pop (0);
22 rwl->r_wait--;
23 }
24 if (status == 0)
25 rwl->r_active++;
26 pthread_mutex_unlock (&rwl->mutex);
27 return status;
28 }

Part 5 shows rwl_readtrylock. This function is nearly identical to rwl_readlock, except that, instead of waiting for access if a writer is active, it returns EBUSY. It doesn't need a cleanup handler, and has no need to increase the count of waiting readers.

This function must also be modified to implement "writer preference" read/ write locks, by returning EBUSY when a writer is waiting, not just when a writer is active.

rwlock.c part 5 rwl_readtrylock

1 /*
2 * Attempt to lock a read/write lock for read access (don't
3 * block if unavailable).
4 */
5 int rwl_readtrylock (rwlock_t *rwl)
6 {
7 int status, status2;

8
9 if (rwl->valid != RWLOCK_VALID)
10  return EINVAL;
11 status = pthread_mutex_lock (&rwl->mutex);
12 if (status != 0)
13  return status;
14 if (rwl->w_active)
15  status = EBUSY;
16 else
17  rwl->r_active++;
18 status2 = pthread_mutex_unlock (&rwl->mutex);
19 return (status2 != 0 ? status2 : status);
20 }

13 Part 6 shows rwl_readunlock. This function essentially reverses the effect of rwl_readlock or rwl_tryreadlock, by decreasing the count of active readers (r_active).

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
9 if (rwl->valid != RWLOCK_VALID)
10  return EINVAL;
11 status = pthread_mutex_lock (&rwl->mutex);
12 if (status != 0)
13  return status;
14 if (rwl->w_active || rwl->r_active > 0)
15  status = EBUSY;
16 else
17  rwl->w_active = 1;
18 status2 = pthread_mutex_unlock (&rwl->mutex);
19 return (status != 0 ? status : status2);
20 }

Finally, part 9 shows rwl_writeunlock. This function is called by a thread with a write lock, to release the lock.

13-19 When a writer releases the read/write lock, it is always free; if there are any threads waiting for access, we must wake one. Because we implement "preferred read" access, we first look for threads that are waiting for read access. If there are any, we broadcast the read condition variable to wake them all.

20-26 If there were no waiting readers, but there are one or more waiting writers, wake one of them by signaling the write condition variable.

To implement a "preferred write" lock, you would reverse the two tests, waking a waiting writer, if any, before looking for waiting readers.

? rwlock.c part 9 rwl_writeunlock

1 /*
2 * Unlock a read/write lock from write access.
3 */
4 int rwl_writeunlock (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 rwl->w_active = 0;
14 if (rwl->r_wait > 0) {
15  status = pthread_cond_broadcast(&rwl->read);
16  if (status != 0) {
17  pthread_mutex_unlock (&rwl->mutex);
18  return status;
19 }
20 } else if (rwl->w_wait > 0) {
21  status = pthread_cond_signal (&rwl->write);
22  if (status != 0) {
23  pthread_mutex_unlock (&rwl->mutex);
24  return status;
25  }
26 }
27 status = pthread_mutex_unlock (&rwl->mutex);
28 return status;
29 }

rwlock.c part 9 writelock

Now that we have all the pieces, rwlock_main.c shows a program that uses read/write locks.

11-17 Each thread is described by a structure of type thread_t. The thread_num member is the thread's index within the array of thread_t structures. The thread_id member is the pthread_t (thread identifier) returned by pthread_ create when the thread was created. The updates and reads members are counts of the number of read lock and write lock operations performed by the thread. The interval member is generated randomly as each thread is created, to determine how many iterations the thread will read before it performs a write.

22-26 The threads cycle through an array of data_t elements. Each element has a read/write lock, a data element, and a count of how many times some thread has updated the element.

48-58 The program creates a set of threads running the thread_routine function. Each thread loops ITERATIONS times, practicing use of the read/write lock. It cycles through the array of data elements in sequence, resetting the index (element) to 0 when it reaches the end. At intervals specified by each thread's interval member, the thread will modify the current data element instead of reading it. The thread locks the read/write lock for write access, stores its thread_num as the new data value, and increases the updates counter.

59-73 On all other iterations, thread_routine reads the current data element, locking the read/write lock for read access. It compares the data value against its thread_num to determine whether it was the most recent thread to update that data element, and, if so, it increments a counter.

95-103 On Solaris systems, increase the thread concurrency level to generate more interesting activity. Without timeslicing of user threads, each thread would tend to execute sequentially otherwise.

? rwlock_main.c

1 #include "rwlock.h"
2 #include "errors.h"

3
4 #define THREADS 5
5 #define DATASIZE 15
6 #define ITERATIONS 10000
7
8 /*
9 * Keep statistics for each thread.
10 */
11 typedef struct thread_tag {
12  int thread_num;
13  pthread_t thread_id;
14  int updates;
15  int reads;
16  int interval;
17 } thread_t;

18
19 /*
20 * Read/write lock and shared data.
21 */
22 typedef struct data_tag {
23  rwlock_t lock;
24  int data;
25  int updates;
26 } data_t;

27
28 thread_t threads[THREADS];
29 data_t data[DATASIZE];

30
31 /*
32 * Thread start routine that uses read/write locks.
33 */
34 void *thread_routine (void *arg)
35 {
36 thread_t *self = (thread_t*)arg;
37 int repeats = 0;
38 int iteration;
39 int element = 0;
40 int status;

41
42 for (iteration = 0; iteration < ITERATIONS; iteration++) {
43 /*
44 * Each "self->interval" iterations, perform an
45 * update operation (write lock instead of read
46 * lock).
47 */
48  if ((iteration % self->interval) == 0) {
49  status = rwl_writelock (&data[element].lock);
50  if (status != 0)
51  err_abort (status, "Write lock");
52  data[element].data = self->thread_num;
53  data[element].updates++;
54  self->updates++;
55  status = rwl_writeunlock(&data[element].lock);
56  if (status != 0)
57  err_abort (status, "Write unlock");
58  } else {
59 /*
60 * Look at the current data element to see whether
61 * the current thread last updated it. Count the
62 * times, to report later.
63 */
64  status = rwl_readlock (&data[element].lock);
65  if (status != 0)
66  err_abort (status, "Read lock");
67  self->reads++;
68  if (data[element].data == self->thread_num)
69  repeats++;
70  status = rwl_readunlock (&data[element].lock);
71  if (status != 0)
72  err_abort (status, "Read unlock");
73  }
74  element++;
75  if (element >= DATASIZE)
76  element = 0;
77 }

78
79 if (repeats > 0)
80  printf (
81  "Thread %d found unchanged elements %d timesn",
82  self->thread_num, repeats);
83  return NULL;
84 }

85
86 int main (int argc, char *argv[])
87 {
88 int count;
89 int data_count;
90 int status;
91 unsigned int seed = 1;
92 int thread_updates = 0;
93 int data_updates = 0;

94
95 #ifdef sun
96 /*
97 * On Solaris 2.5, threads are not timesliced. To ensure
98 * that our threads can run concurrently, we need to
99 * increase the concurrency level to THREADS.
100 */
101 DPRINTF (("Setting concurrency level to %dn", THREADS));
102 thr_setconcurrency (THREADS);
103 #endif

104
105 /*
106 * Initialize the shared data.
107 */
108 for (data_count = 0; data_count < DATASIZE; data_count++) {
109  data[data_count].data = 0;
110  data[data_count].updates = 0;
111  status = rwl_init (&data[data_count].lock);
112  if (status != 0)
113  err_abort (status, "Init rw lock");
114 }

115
116 /*
117 * Create THREADS threads to access shared data.
118 */
119 for (count = 0; count < THREADS; count++) {
120  threads[count].thread_num = count;
121  threads[count].updates = 0;
122  threads[count].reads = 0;
123  threads[count].interval = rand_r (&seed) % 71;
124  status = pthread_create (&threads[count].thread_id,
125  NULL, thread_routine, (void*)&threads[count]);
126  if (status != 0)
127  err_abort (status, "Create thread");
128 }

129
130 /*
131 * Wait for all threads to complete, and collect
132 * statistics.
133 */
134 for (count = 0; count < THREADS; count++) {
135  status = pthread_join (threads[count].thread_id, NULL);
136  if (status != 0)
137  err_abort (status, "Join thread");
138  thread_updates += threads[count].updates;
139  printf ("%02d: interval %d, updates %d, reads %dn",
140  count, threads[count].interval,
141  threads[count].updates, threads[count].reads);
142 }

143
144 /*
145 * Collect statistics for the data.
146 */
147 for (data_count = 0; data_count < DATASIZE; data_count++) {
148  data_updates += data[data_count].updates;
149  printf ("data %02d: value %d, %d updatesn",
150  data_count, data[data_count].data,
151  data[data_count].updates);
152  rwl_destroy (&data[data_count].lock);
153 }

154
155 printf ("%d thread updates, %d data updatesn",
156 thread_updates, data_updates);
157 return 0;

Îãëàâëåíèå êíèãè

Îãëàâëåíèå ñòàòüè/êíèãè

Ãåíåðàöèÿ: 0.069. Çàïðîñîâ Ê ÁÄ/Cache: 2 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç