there are free processors. The following program, inertia.c, demonstrates how this phenomenon can disrupt your program.
27-41 The question is one of whether the thread function printer_thread will see the value of stringPtr that was set before the call to pthread_create, or the value set after the call to pthread_create. The desired value is 'After value.' This is a very common class of programming error. Of course, in most cases the problem is less obvious than in this simple example. Often, the variable is uninitialized, not set to some benign value, and the result may be data corruption or a segmentation fault.
39 Now, notice the delay loop. Even on a multiprocessor, this program won't break all the time. The program will usually be able to change stringPtr before the new thread can begin executing — it takes time for a newly created thread to get into your code, after all, and the 'window of opportunity' in this particular program is only a few instructions. The loop allows me to demonstrate the problem by delaying the main thread long enough to give the printer thread time to start. If you make this loop long enough, you will see the problem even on a uniprocessor, if main is eventually timesliced.
¦ inertia.c
1 #include <pthread.h>
2 #include 'errors.h'
3
4 void *printer_thread (void *arg)
5 {
6 char *string = *(char**)arg;
7
8 printf ('%s
', string);
9 return NULL;
10 }
11
12 int main (int argc, char *argv[])
13 {
14 pthread_t printer_id;
15 char *string_ptr;
16 int i, status; 17
18 #ifdef sun
19 /*
20 * On Solaris 2.5, threads are not timesliced. To ensure
21 * that our two threads can run concurrently, we need to
22 * increase the concurrency level to 2.
23 */
24 DPRINTF (('Setting concurrency level to 2
'));
25 thr_setconcurrency (2);
26 #endif
27 string_ptr = 'Before value';
28 status = pthread_create (
29 &printer_id, NULL, printer_thread, (void*)&string_ptr);
30 if (status != 0)
31 err_abort (status, 'Create thread');
32
33 /*
34 * Give the thread a chance to get started if it's going to run
35 * in parallel, but not enough that the current thread is likely
36 * to be timesliced. (This is a tricky balance, and the loop may
37 * need to be adjusted on your system before you can see the bug.)
38 */
39 for (i = 0; i < 10000000; i++);
40
41 string_ptr = 'After value';
42 status = pthread_join (printer_id, NULL);
43 if (status != 0)
44 err_abort (status, 'Join thread');
45 return 0;
46 }
The way to fix inertia.c is to set the 'After value,' the one you want the threads to see, before creating the thread. That's not so hard, is it? There may still be a 'Before value,' whether it is uninitialized storage or a value that was previously used for some other purpose, but the thread you create can't see it. By the memory visibility rules given in Section 3.4, the new thread sees all memory writes that occurred prior to the call into pthread_create. Always design your code so that threads aren't started until after all the resources they need have been created and initialized exactly the way you want the thread to see them.
Never assume that a thread you create will wait for you.
You can cause yourself as many problems by assuming a thread will run 'soon' as by assuming it won't run 'too soon.' Creating a thread that relies on 'temporary storage' in the creator thread is almost always a bad idea. I have seen code that creates a series of threads, passing a pointer to the same local structure to each, changing the structure member values each time. The problem is that you can't assume threads will start in any specific order. All of those threads may start after your last creation call, in which case they all get the last value of the data. Or the threads might start a little bit out of order, so that the first and second thread get the same data, but the others get what you intended them to get.
Thread inertia is a special case of thread races. Although thread races are covered much more extensively in Section 8.1.2, thread inertia is a subtle effect, and many people do not recognize it as a race. So test your code thoroughly on a multiprocessor, if at all possible. Do this as early as possible during development, and continuously throughout development. Do this despite the fact that, especially without a perfect threaded debugger, testing on a multiprocessor will be more difficult than debugging on a uniprocessor. And, of course, you should carefully read the following section.
8.1.2 Never bet your mortgage on a thread race
A race occurs when two or more threads try to get someplace or do something at the same time. Only one can win. Which thread wins is determined by a lot of factors, not all of which are under your control. The outcome may be affected by how many processors are on the system, how many other processes are running, how much network overhead the system is handling, and other things like that. That's a nondeterministic race. It probably won't come out the same if you run the same program twice in a row. You don't want to bet on races like that.[9]
When you write threaded code,assume that at any arbitrary point, within any statement of your program,each thread may go to sleep for an unbounded period of time.
Processors may execute your threads at differing rates, depending on processor load, interrupts, and so forth. Timeslicing on a processor may interrupt a thread at any point for an unspecified duration. During the time that a thread isn't running, any other thread may run and do anything that synchronization protocols in your code don't specifically prevent it from doing, which means that between any two instructions a thread may find an entirely different picture of memory, with an entirely different set of threads active. The way to protect a thread's view of the world from surprises is to rely only on explicit synchronization between threads.
Most synchronization problems will probably show up pretty quickly if you're debugging on a multiprocessor.