views:

84

answers:

4

Hi,

I have this POSIX thread:

void subthread(void)
{
  while(!quit_thread) {

     // do something
     ...

     // don't waste cpu cycles
     if(!quit_thread) usleep(500);
  }

  // free resources
  ...

  // tell main thread we're done
  quit_thread = FALSE;
}

Now I want to terminate subthread() from my main thread. I've tried the following:

quit_thread = TRUE;

// wait until subthread() has cleaned its resources
while(quit_thread);

But it does not work! The while() clause does never exit although my subthread clearly sets quit_thread to FALSE after having freed its resources!

If I modify my shutdown code like this:

quit_thread = TRUE;

// wait until subthread() has cleaned its resources
while(quit_thread) usleep(10);

Then everything is working fine! Could someone explain to me why the first solution does not work and why the version with usleep(10) suddenly works? I know that this is not a pretty solution. I could use semaphores/signals for this but I'd like to learn something about multithreading, so I'd like to know why my first solution doesn't work.

Thanks!

+1  A: 

The while(quite_thread); is using the value quit_thread was set to on the line before it. Calling a function (usleep) induces the compiler to reload the value on each test.

In any case, this is the wrong way to wait for a thread to complete. Use pthread_join instead.

Marcelo Cantos
+2  A: 

Without a memory fence, there is no guarantee that values written in one thread will appear in another. Most of the pthread primitives introduce a barrier, as do several system calls such as usleep. Using a mutex around both the read and write introduces a barrier, and more generally prevents multi-byte values being visible in partially written state.

You also need to separate the idea of asking a thread to stop executing, and reporting that it has stopped, and appear to be using the same variable for both.

Pete Kirkham
A: 

You're "learning" multhithreading the wrong way. The right way is to learn to use mutexes and condition variables; any other solution will fail under some circumstances.

zvrba
A: 

What's most likely to be happening is that your compiler is not aware that quit_thread can be changed by another thread (because C doesn't know about threads). Because of that, it's optimising the while loop to an infinite loop.

In other words, it looks at this code:

quit_thread = TRUE;
while(quit_thread);

and thinks to itself, "Hah, nothing in that loop can ever change quit_thread to FALSE, so the coder obviously just meant to write while (TRUE);".

When you add the call to usleep, the compiler has another think about it and assumes that the function call may change the global, so it plays it safe and doesn't optimise it.

Normally you would mark the variable as volatile to stop the compiler from optimising it but, in this case, you should use the facilities provided by pthreads and join to the thread after setting the flag to true (and don't have the sub-thread reset it, do that in the main thread after the join if it's necessary). The reason for that is that a join is likely to be more efficient than a continuous loop waiting for a variable change since the thread doing the join will most likely not be executed until the join needs to be done.

In your spinning solution, the joining thread will most likely continue to run and suck up CPU grunt.

In other words, do something like:

Main thread              Child thread
-------------------      -------------------
fStop = false
start Child              Initialise
Do some other stuff      while not fStop:
fStop = true                 Do what you have to do
                         Finish up and exit
join to Child
Do yet more stuff

And, as an aside, you should technically protect shared variables with mutexes but this is one of the few cases where it's okay, one-way communication where half-changed values of a variable don't matter (false/not-false).

The reason you normally mutex-protect a variable is to stop one thread seeing it in a half-changed state. Let's say you have a two-byte integer for a count of some objects, and it's set to 0x00ff (255).

Let's further say that thread A tries to increment that count but it's not an atomic operation. It changes the top byte to 0x01 but, before it gets a chance to change the bottom byte to 0x00, thread B swoops in and reads it as 0x01ff.

Now that's not going to be very good if thread B want to do something with the last element counted by that value. It should be looking at 0x0100 but will instead try to look at 0x01ff, the effect of which will be wrong, if not catastrophic.

If the count variable were protected by a mutex, thread B wouldn't be looking at it until thread A had finished updating it, hence no problem would occur.

The reason that doesn't matter with one-way booleans is because any half state will also be considered as true or false so, if thread A was halfway between turning 0x0000 into 0x0001 (just the top byte), thread B would still see that as 0x0000 (false) and keep going (until thread A finishes its update next time around).

And if thread A was turning the boolean into 0xffff, the half state of 0xff00 would still be considered true by thread B so it would do its thing before thread A had finished updating the boolean.

Neither of those two possibilities is bad simply because, in both, thread A is in the process of changing the boolean and it will finish eventually. Whether thread B detects it a tiny bit earlier or a tiny bit later doesn't really matter.

paxdiablo