ThreadMentor: Joining Threads

Thread join in ThreadMentor is very simple. The base class Thread has a method Join() for this purpose. Suppose thread A wants to join with thread B. That is, thread A wants to wait until thread B completes. To this end, thread A simply calls thread B's method Join(). In other words, at the point where thread A wants to wait for B's completion, thread A executes B.Join() or B->Join(), depending on how thread B is created.

For example, the main program of an example on the previous page does not have thread join, and, as a result, it is likely that the main thread completes before all of its child threads. Should this happen, all child threads are forced to terminate. To overcome this problem, after all three child threads are created, the main thread should execute three thread joins, one for each child threads, as shown below:

void  main(void)
{
     TestThread* Running[3];
     int         i;

     for (i = 0; i < 3; i++) {
          Running[i] = new TestThread(i);
          Running[i]->Begin();
     }
     for (i = 0; i < 3; i++)
          Running[i]->Join();
     Exit();
}

In this example, the main thread creates and runs three child threads. After this step, the main thread executes three thread joins. The order of this three joins is Running[0], followed by Running[1], followed by Running[2]. Thus, when the main thread executes the first join and Running[0] is still running, the main thread waits until Running[0] completes. Then, the main thread executes the second join. If Running[0] has already terminated when the main thread executes the first join, the main thread simply loops back and execute the second. Thus, in this particular case, the order of executing these thread joins is unimportant. However, in some cases, the order is important.

How about the following? This is a commonly seen beginner mistake. We create and run a thread. Then, we join with it. What is wrong? Think about this before continue.

void  main(void)
{
     TestThread* Running[3];
     int         i;

     for (i = 0; i < 3; i++) {
          Running[i] = new TestThread(i);
          Running[i]->Begin();
          Running[i]->Join();
     }
     Exit();
}

This program looks quite normal and correct. In fact, it is not. We create and run the first thread Running[0]. At this point, we have two running threads, the main thread and Running[0]. Then, the main thread immediately wants to join with Running[0]. Thus, the main thread continues only when Running[0] terminates. This holds true for the other two threads. As a result, the execution order will look like the following:

More precisely, the threaded program becomes serialized, and at any moment there is only one thread running. This defeats the whole purpose of using threads to gain efficiency.