Moby Disk Consulting
Software Development, Training & Consulting
William Garrison - mobydisk at mobydisk daht com

POSIX threads tutorial

This is a short tutorial on how to code POSIX threads in C++ on GNU/Linux. For up-to-date information on the Linux implementation of pthreads called LinuxThreads, check out Xavier Leroy's page.

What you need

Any modern Linux should be able to do this. For specific requirements, check outthe link above. This tutorial does not explain what threads are, how they work, or how to synchronize multiple threads. It assumes you know what you plan to use threads for, and that you have a good reference on synchronizing threads. If you don't have any of this information, then visit the Links section at the bottom of this page.

Compiling this demo

At your Linux prompt type:

   g++ threaddemo.cpp -o threaddemo -lpthread -D_REENTRANT

then:

   ./threaddemo

Program description

This sample program will create 2 threads. One thread displays the word "ping" and the other displays "pong." The resulting output will look something like this:

ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,
ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,
ping,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,
pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,
pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,
pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,pong,ping,
Done!

Sample source

The sample source code is threaddemo.cpp. You will also need Morgan McGuire's great kbhit.cpp which makes things a little easier.

The how-to

The pthreads library hides out in the pthread.h file, as you can see below.

#include <pthread.h>
#include <iostream>
#include "kbhit.cpp"

// On Linux, you must compile with the -D_REENTRANT option.  This tells
// the C/C++ libraries that the functions must be thread-safe
#ifndef _REENTRANT
#error ACK! You need to compile with _REENTRANT defined since this uses threads
#endif

Whenever compiling an application for pthreads, you must use the -D_REENTRANT option to inform the C/C++ libraries that they are in multithread mode. The #ifndef _REENTRANT line verifies that this is the case before allowing the compile to continue. Further, you should only make calls to other libraries that were compiled with -D_REENTRANT. If you need to know if your favorite library is compiled with this code, seek the author or the documentation. For more information about this subject see the links at the bottom of this page.

volatile int bStop = false;

The bStop variable is used to tell the threads when to stop. If the variable is not declared volatile, then the compiler may think that the value is always false, and may not even pay attention to it! More later.

////////////////////////////////////////////////////////////////////////////////
// Starting point for a thread
////////////////////////////////////////////////////////////////////////////////
void * runMe(void *generic_pointer)
{
   // Do this until we are told to stop
   while (!bStop)
   {
      // Output some text
      cout << static_cast<char *>(generic_pointer) << ",";
      // Relenquish the CPU for another thread
      pthread_yield();
   }
   
   return NULL;
}

This routine will be executed in each of the 2 threads that we will create. The function takes a void pointer, and returns a void pointer. This is necessary, since it is a requirement of the pthread_create function that we are about to use. The parameter, and the return value are arbitrary; that is, they can be used for whatever purpose you wish.

In this demo, the parameter is assumed to be a char * that holds a string for the thread to display. In a real application, this parameter may be a pointer to a structure with information about what the thread needs to do. For example, imagine a multithreaded game, with a thread controlling each of the enemy characters. This parameter may be a pointer to the character's information, with the character position, intelligence, weapons, etc.

The return value is not used here. It could be treated similarly to the parameter, in that it can be used to return some arbitrary data to the parent thread. It could be a pointer to an integer return value indicating success or failure, or an exception structure, or error message.

The basic function of this thread is to display a message over and over, while not using up the entire CPU. The thread avoids hogging the CPU by calling pthread_yield() after displaying the message. This function tells the OS that the thread isn't busy and something else can go happen now. Note that the OS may not wait until the thread calls pthread_yield to switch threads. It may happen at any point. This call just tells the OS that this would be a good time to do so. More on this later.

The thread will stop displaying the message once the value of bStop becomes true. At this point, the thread will exit with a return value of NULL. This return statement does not return control to the caller: it stops the thread from executing entirely. This is the equivalent to calling pthread_exit(NULL).

   pthread_t nThreadID1, nThreadID2;

Every thread has an ID. Nothing special there.

   pthread_create(&nThreadID1, NULL, runMe, szMessage1);
   pthread_create(&nThreadID2, NULL, runMe, szMessage2);

This code is kinda like doing:

    runMe(szMessage1);
   runMe(szMessage2);

Except that the code executes in the background, instead of sequentially.

After this code, there are 3 threads: the parent, and 2 children. Let's check out the pthread_create call. The first parameter is the address of nThreadID. pthread_create will stuff the thread ID into this. The second parameter has all sorts of nifty stuff about the thread: priority, scheduling, stack size, whatever. We don't care about any of this so it is NULL. Next is the name of the function to use as the starting point for the thread. The function must take a void * and return a void *. Lots of tutorials do this incorrectly, and must cast the function with some crazy C-style weirdness. If you declare the function exactly right, you can just place the name there and nothing else. The last function is the generic parameter, which we use for the message.

   while (!_kbhit())
   {
            pthread_yield();
   }

At this point the child threads are running. The parent thread now loops until the user presses a key. It is crucial that this loop call pthread_yield() or the child threads may not execute. This is because the parent thread would spend all its time checking for a key, and never giving anyone else a chance to work. This behavior of threads varies with operating system, kernel, processor, phase of the moon, etc. Pthreads makes no guarantee that each thread executes "fairly." Threads may be interrupted at any time, for any length of time. If this isn't appropriate, then you may need some thread synchronization code, or to provide a hint during the pthread_create call about how you want the threads to behave.

   bStop = true;

This changes the value of the bStop variable. When the threads check this value they will exit. If this value were not declared volatile, then it is possible that the threads may not notice it, and continue running forever!

   pthread_join(nThreadID1, NULL);
   pthread_join(nThreadID2, NULL);   

The pthread_join call tells a thread to wait until the specified thread has exited. This is why we saved the thread IDs. What happens if we remove this code? In this demo, nothing. The child threads will stop anyway, once the parent thread exits. In more complex code, you may need to wait for your threads to free their memory, close open files, etc. before your application exits. The pthread_join call also allows you to see the return value of the thread. Remember the void * return value? The second parameter to pthread_join() tells you where to put this value. For this application, we don't care what they returned.

Further improvements

A nifty improvement (left to the reader, of course) would be to have each thread return the number of times it displayed the message. In a perfect world, it would be the same for each thread. But as we know, threads aren't guaranteed fair excecution time!

In theory, this code is POSIX compliant (except for the kbhit.cpp) and should run on other operating systems. You may wish to compile this code and run it on Coolix or Niftyux or FreeBVD or whatever and see if it works.

Links

Most of these are pretty much the first things you find in a search for threads, linux, and POSIX or some combination thereof.