Pthreads Tutorial

*******
Separator
*******

Overview

The following tutorial should help you to get started with pthreads.  You should also consult the man pages on the solaris machines.  Much of this tutorial was taken from material presented by Andreae Muys.

Benefits of Concurrency

There are a number of situations where threads can greatly simplify writing elegant and efficient programs. The sorts of problems where threads can be very useful include.

Blocking IO.

Programs that do a lot of IO have three options. They can either do the IO serially, waiting for each to complete before commencing the next. They can use Asynchronous IO, dealing with all the complexity of asynchronous signals, polling or selects. They can use synchronous IO, and just spawn a sperarate thread/process for each IO call. In this case threading can significantly improve both performance and code complexity.

Multiple Processors.

If you are using a threads library that supports multiple processors, you can gain significant performance improvements by running threads on each processor. This is particularly useful when your program is compute bound.

User Interface.

By separating the user interface, and the program engine into different threads you can allow the UI to continue to respond to user input even while long operations are in progress.

Servers.

Servers that serve multiple clients can be made more responsive by the appropriate use of concurrency. This has traditionally been achieved by using the fork() system call. However in some cases, especially when dealing with large caches, threads can help improve the memory utilisation, or even permit concurrent operation where fork() was unsuitable.

 

However there are problems when multiple threads share a common address space. The biggest problem concerns data races.


Consider the following code:

 

THREAD 1               THREAD 2
a = data;              b = data;
a++;                   b--;
data = a;              data = b;
 

Now if this code is executed serially (THREAD 1, the THREAD 2) there isn't a problem. However threads execute in an arbitrary order, so consider this:

 

THREAD 1               THREAD 2
a = data;
                       b = data;
a++;
                       b--;
data = a;
                       data = b;
 
[data = data - 1!!!!!!!]
 

So data could end up +1, 0, -1, and there is NO way to know which as it is completely non-deterministic!

In fact the problem is worse, because on many machines a = data is non-atomic. This means that when data is loaded into a, you could end up with the low order bits of the old data, and the high order bits of the new data. CHAOS.

The solution to this is to provide functions that will block a thread if another thread is accessing data that it is using.

Pthreads use a data type called a mutex to achieve this.

The Pthreads Library

*******
Separator
*******

Creating a POSIX thread.

Pthreads are created using pthread_create().

#include 
 
int 
pthread_create (pthread_t *thread_id, const pthread_attr_t *attributes,
               void *(*thread_function)(void *), void *arguments);

This function creates a new thread. pthread_t is an opaque type which acts as a handle for the new thread. attributes is another opaque data type which allows you to fine tune various parameters, to use the defaults pass NULL. thread_function is the function the new thread is executing, the thread will terminate when this function terminates, or it is explicitly killed. arguments is a void * pointer which is passed as the only argument to the thread_function.

Pthreads terminate when the function returns, or the thread can call pthread_exit() which terminates the calling thread explicitly.

int
pthread_exit (void *status);

status is the return value of the thread. (note a thread_function returns a void *, so calling return(void *) is the equivalent of this function.

One Thread can wait on the termination of another by using pthread_join()

int
pthread_join (pthread_t thread, void **status_ptr);

The exit status is returned in status_ptr.

A thread can get its own thread id, by calling pthread_self()

pthread_t
pthread_self ();

Two thread id's can be compared using pthread_equal()

int
pthread (pthread_t t1, pthread_t t2);

Returns zero if the threads are different threads, non-zero otherwise.

Mutexes

Mutexes have two basic operations, lock and unlock. If a mutex is unlocked and a thread calls lock, the mutex locks and the thread continues. If however the mutex is locked, the thread blocks until the thread 'holding' the lock calls unlock.

There are 5 basic functions dealing with mutexes.

int
pthread_mutex_init (pthread_mutex_t *mut, const pthread_mutexattr_t *attr);

Note that you pass a pointer to the mutex, and that to use the default attributes just pass NULL for the second parameter.

int
pthread_mutex_lock (pthread_mutex_t *mut);

Locks the mutex :).

int
pthread_mutex_unlock (pthread_mutex_t *mut);

Unlocks the mutex :).

int
pthread_mutex_trylock (pthread_mutex_t *mut);

Either acquires the lock if it is available, or returns EBUSY.

int
pthread_mutex_destroy (pthread_mutex_t *mut);

Deallocates any memory or other resources associated with the mutex.

A short example

Consider the problem we had before, now lets use mutexes:

THREAD 1                       THREAD 2
pthread_mutex_lock (&mut);     
                               pthread_mutex_lock (&mut); 
a = data;                      /* blocked */
a++;                           /* blocked */
data = a;                      /* blocked */
pthread_mutex_unlock (&mut);   /* blocked */
                               b = data;
                               b--;
                               data = b;
                               pthread_mutex_unlock (&mut);
[data is fine.  The data race is gone.]

You will need to be careful to avoid this kind of critical section in all of the layers in your protocol stack or you will have a bunch of hard to find bugs.

Your general code format should be

class physical {
public:
 int socket;
// The method passed to pthread_create must be static
static void *readloop(void *arg)
{
  foobar *cls = (physical *)arg;
  
  int i;

  for(;;) {
    printf("This is the thread socket %d\n",cls->socket);
    // Read from the socket
  }
}

int init(...)
{
  pthread_t mythread;
  int val;
  val = pthread_create (&mythread, NULL, physical::readloop, (void *)this);
  printf("val %d\n",val);
  socket = 45;
}
}
In the initialization code you should call pthread_create with the routine you are using to read data.

The threads library will call the routine "readloop" with the "this" pointer to the physical object. You should then have the socket, buffers and other data you will need to access in your reader method. 

In the readloop method, you should read until you get 90 bytes and then call the pop routine. This routine should call the pop routine in the next layer up and so forth.  I am sure that there are better ways of doing this, but you might start with something like this until you get things working.

for(;;) {
  total=0;
  while(total < 90) {
    rval = read(socket, buffer+total, 90-total);
    if(rval > 0) {
      total += rval;
    } else {
      exit(-1);
    }
  }
  cls->prot_higher->pop(char *buffer, int total);
}