79
80 /*
81 * Loop, making requests.
82 */
83 for (count = 0; count < ITERATIONS; count++) {
84 element = (power_t*)malloc (sizeof (power_t));
85 if (element == NULL)
86 errno_abort ('Allocate element');
87 element->value = rand_r (&seed) % 20;
88 element->power = rand_r (&seed) % 7;
89 DPRINTF ((
90 'Request: %d^%d
',
91 element->value, element->power));
92 status = workq_add (&workq, (void*)element);
93 if (status != 0)
94 err_abort (status, 'Add to work queue');
95 sleep (rand_r (&seed) % 5);
96 }
97 return NULL;
98 } 99
100 int main (int argc, char *argv[])
101 {
102 pthread_t thread_id;
103 engine_t *engine;
104 int count = 0, calls = 0;
105 int status;
106
107 status = pthread_key_create (&engine_key, destructor);
108 if (status != 0)
109 err_abort (status, 'Create key');
110 status = workq_init (&workq, 4, engine_routine);
111 if (status != 0)
112 err_abort (status, 'Init work queue');
113 status = pthread_create (&thread_id, NULL, thread_routine, NULL);
114 if (status != 0)
115 err_abort (status, 'Create thread');
116 (void)thread_routine (NULL);
117 status = pthread_join (thread_id, NULL);
118 if (status != 0)
119 err_abort (status, 'Join thread');
120 status = workq_destroy (&workq);
121 if (status != 0)
122 err_abort (status, 'Destroy work queue'); 123
124 /*
125 * By now, all of the engine_t structures have been placed
126 * on the list (by the engine thread destructors), so we
127 * can count and summarize them.
128 */
129 engine = engine_list_head;
130 while (engine != NULL) {
131 count++;
132 calls += engine->calls;
133 printf ('engine %d: %d calls
', count, engine->calls);
134 engine = engine->link;
135 }
136 printf ('%d engine threads processed %d calls
',
137 count, calls);
138 return 0;
139 }
7.3 But what about existing libraries?
'The great art of riding, as I was saying is—
to keep your balance properly. Like this, you know—' He let go the bridle, and stretched out both his arms to
show Alice what he meant, and this time he fell flat on
his back, right under the horse's feet.
When you create a new library, all it takes is careful design to ensure that the library will be thread-safe. As you decide what state is needed for the function, you can determine which state needs to be shared between threads, which state should be managed by the caller through external context handles, which state can be kept in local variables within a function, and so forth. You can define the interfaces to the functions to support that state in the most efficient manner. But when you're modifying an existing library to work with threads, you usually don't have that luxury. And when you are using someone else's library, you may need simply to 'make do.'
7.3.1 Modifying libraries to be thread-safe
Many functions rely on static storage across a sequence of calls, for example, strtok or getpwd. Others depend on returning a pointer to static storage, for example, asctime. This section points out some techniques that can help when you need to make 'legacy' libraries thread-safe, using some well-known examples in the ANSI C run- time library.
The simplest technique is to assign a mutex to each subsystem. At any call into the subsystem you lock the mutex; at any exit from the subsystem you unlock the mutex. Because this single mutex covers the entire subsystem, we often refer to such a mechanism as a 'big mutex' (see Section 3.2.4). The mutex prevents more than one thread from executing within the subsystem at a time. Note that this fixes only
One problem with using the 'big mutex' approach is that you have to be careful about your definition of 'subsystem.' You need to include all functions that share data or that call each other. If malloc and free have one mutex while realloc uses another, then you've got a race as soon as one thread calls realloc while another thread is