Thread parameters – what not to do

The really key thing to keep in mind when passing a parameter to a thread routine is that you must guarantee that the parameter passed along is thread-safe; essentially, that it does not get modified in any manner while a thread (or threads) are using it.

(Thread safety is a crucial aspect of working with threads; we shall revisit this point often in upcoming chapters, too). 

To help understand the possible issues clearly, let's take a couple of typical examples. In the first one, we shall (attempt to) pass the loop index as the parameter to the newly born thread such as, in main (code: ch14/pthreads1_wrong.c):

 printf("main: &i=%p
", &i);
for (i = 0; i < NTHREADS; i++) {
printf("Creating thread #%ld now ... ", i);
ret = pthread_create(&tid, NULL, worker, (void *)&i);
...
}

Did you notice!? We have passed the parameter as &i. So? Dereferencing it correctly in the thread routine should still work, right:

void * worker(void *data)
{
long data_addr = (long)data;
long index = *(long *)data_addr;
printf("Worker thread: data_addr=%p value=%ld ",
(void *)data_addr, index);
pthread_exit((void *)0);
}

Looks okay  let's give it a try!

$ ./pthreads1_wrong
main: &i=0x7ffebe160f00
Creating thread #0 now ...
Creating thread #1 now ...
Worker thread: data_addr=0x7ffebe160f00 value=1
Creating thread #2 now ...
Worker thread: data_addr=0x7ffebe160f00 value=2
Worker thread: data_addr=0x7ffebe160f00 value=3
$

Well, it works. But hang on, try it a few more times—timing coincidences can fool you into thinking that all's well when it's really not:

$ ./pthreads1_wrong
main: &i=0x7fff4475e0d0
Creating thread #0 now ...
Creating thread #1 now ...
Creating thread #2 now ...
Worker thread: data_addr=0x7fff4475e0d0 value=2
Worker thread: data_addr=0x7fff4475e0d0 value=2
Worker thread: data_addr=0x7fff4475e0d0 value=3
$

 

There's a bug! The index value has evaluated to the value 2 twice; why? Think carefully: we have passed the loop index by reference – as the pointer to the loop variable. Thread 1 comes alive, and looks up its value – so does thread 2, as does thread 3. But wait: isn't it possible that we have a race here? Isn't it possible that by the time thread 1 runs and looks up the value of the loop variable it has already changed underneath it (because, don't forget, the loop is running in main)? That, of course, is precisely what happened in the preceding code.

In other words, passing the variable by address is unsafe because its value could change while it is being read (by the worker threads) as it being simultaneously written to (by main); hence, it's not thread-safe and therefore will be buggy (racy). 

The solution is actually really simple: do not pass the loop index by address; just pass it as a literal value:

 

for (i = 0; i < NTHREADS; i++) {
printf("Creating thread #%ld now ... ", i);
ret = pthread_create(&tid, NULL, worker, (void *)i);
...
}

Now, each worker thread receives a copy of the loop index, thus eliminating any race, thus making it safe.

Now, don't jump to the conclusion that, hey, okay, so we should never pass a pointer (an address) as a parameter. Of course you can! Just ensure that it's thread-safe – that its value cannot change underneath it while being manipulated by main and the other application threads.

Refer back to the ch14/struct_as_param.c code we demonstrated in the previous section; we very much pass the thread parameter as a pointer to a structure. Look closely: each pointer was separately allocated (via calloc(3)) in the main thread creation loop. Thus, each worker thread received its own copy of the structure; hence, all is safe and it works well.

An interesting exercise (that we leave to the reader) is to deliberately insert a defect into the struct_as_param application by using exactly one allocated structure (not three) and passing it to each of the worker threads. This time, it will be racy and will (eventually) fail.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset