POSIX Threads

Back to main page

Emanuele Altieri (ealtieri@cs.smith.edu)
Prof. Nicholas Howe (nhowe@cs.smith.edu)
Smith College, June 2002

Contents

  1. Getting started with threads
  2. Passing arguments to threads
  3. Synchronization between threads

Getting started with threads

Threads are parallel, independent execution units which share the same code and data segments. A process is a collection of one or more execution threads. Processes characterized by only one thread of execution are called single-threaded. On the other hand, a multi-threaded process has multiple threads of execution (see picture below).

threads

A new process always starts with a single thread, the parent or original thread (P in the figure above). During its execution, a thread can generate other threads of execution using the pthread_create() function. In the figure above, the A, B and C threads originated from the P thread.

The behaviour of pthread_create() is similar to the fork() routine, described in Processes and Process Scheduling: Process Creation, which allows new processes to be forked from an original process. However, differently from a child process originated from fork(), a newly created thread shares its global variables (data segment) with all of the other threads belonging to the same process.

question In the last paragraph we explained the similarities between the fork() and pthread_create() functions. fork() splits the current process into two child processes, while pthread_create() splits the current thread into two threads of execution. Can you guess which function is faster? Explain your answer.

pthread_create() is defined in /usr/include/pthread.h (notice that it is not part of the Linux kernel):

int pthread_create(pthread_t *thread_id, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

On success, a thread is created and its identifier stored in the variable pointed by thread_id. The thread starts executing from the start_routine function. Notice the format of the start_routine: the function must return a void * pointer, and take a void * as argument. For example:

int main(void)
{
	pthread_t id;

        pthread_create(&id, NULL, &my_thread, NULL);   /* create thread */

	/* ORIGINAL THREAD */

	/* do something... */

	pthread_exit(NULL);
}

static void *my_thread(void *p)
{
	/* NEW THREAD */

	/* do something else... */

	pthread_exit(NULL);
}

After the call to pthread_create(), the main() and my_thread() functions will run concurrently, each located on a different thread of the same process.

Here, once again, the behaviour of pthread_create() differs from fork(). While fork() begins execution of the new process exactly from its calling point, pthread_create() begins execution of a new thread from the function passed as the start_routine parameter.

Now consider another similar example:

static int main_lock = 1;

int main(void)
{
	pthread_t id;
        pthread_create(&id, NULL, &my_thread, NULL);  /* create thread */
        while(main_lock); /* busy loop */

	/* PART A */

	pthread_exit(NULL);
}

static void *my_thread(void *p)
{
	/* PART B (critical) */

        main_lock = 0;

	/* PART C */

	pthread_exit(NULL);
}

Two threads are created in the same way we saw in the previous example. However, in this case we introduce a global variable, main_lock. The main() thread waits for this variable to become zero before entering part A of its code. The second thread, my_thread(), sets this lock to zero at some point during its execution.

The example above shows a very primitive way to synchronize threads. This code works because threads of the same process share the same data segment, and therefore the same global variables. A modification to main_lock by one thread is also visible to the other thread.

question In the last example, what would have happened if the threads didn't share the same data segment (such as with fork())?
question Think of examples in which threads can be useful.

Passing arguments to threads

It is possible to pass arguments to new threads through the arg parameter of pthread_create(). For example:

int main(void)
{
        int element;               
        ...
        pthread_create(&id, NULL, &my_thread, &element);
        ...
	pthread_exit(NULL);
}

static void *my_thread(void *p)
{
        int arg = *p;  /* retrieve argument */
	printf("The argument passed to the thread is %d\n", arg);
	pthread_exit(NULL);
}

Notice that we can only pass pointers through pthread_create(). This limitation introduces a problem: it is possible that the argument passed to a new thread is modified by the original thread before the new thread can actually retrieve it. Consider the example below:

int main(void)
{
        int element;               
        ...
        element = 5;
        pthread_create(&id, NULL, &my_thread, &element);
        element = 3;
        ...
	pthread_exit(NULL);
}

static void *my_thread(void *p)
{
        int arg = *p;  /* retrieve argument */
	printf("The argument passed to the thread is %d\n", arg);
	pthread_exit(NULL);
}

We cannot be sure of which value of element will be read by the new thread. This is because we do not know the order in which the threads are executed in a multiprogramming environment.

question Provide a solution for the "element" problem in the last example
question What is wrong with the following code?
pthread_t threads[NUM_THREADS];

for(t=0; t<NUM_THREADS; t++)
{
   printf("Creating thread %d\n", t);
   rc = pthread_create(&threads[t], NULL, &my_thread, (void *) &t);
   ...
}
Provide a correct alternative to the code above.

Synchronization between threads

Earlier in this document, we implemented a very primitive synchronization method between two threads (see here). The implementation was making use of a busy loop based on the global variable main_lock.

The busy loop method works fine in the earlier example. However, as we also explain in Kernel Synchronization: Spin Locks, busy loops are really expensive from the point of view of the processor. In addition, as the complexity and number of threads of a process increase, busy loops and global variables become very impractical.

POSIX threads implement very powerful and inexpensive methods of synchronization between threads using mutex and conditions. We will describe these two methods in the following paragraphs.

Mutex

A mutex is a mutual exclusion device, and is useful for protecting shared data structures from concurrent modifications, and implementing critical sections and monitors.

A mutex has two possible states: unlocked (not owned by any thread), and locked (owned by one thread). A mutex can never be owned by two different threads simultaneously. A thread attempting to lock a mutex that is already locked by another thread is suspended until the owning thread unlocks the mutex first.

A mutex is declared as pthread_mutex_t data type.

Some of the POSIX functions that operate on a mutex are described below. Detailed information about each of these functions can be found in their manual pages.

In the following example we show two threads accessing the same global variables. These variables are protected by a mutex.

#include <pthread.h>

#define MAX_BUFFER 1000
int buffer[MAX_BUFFER];
int count = 0;

pthread_mutex_t mutex;   /* mutex */

void *my_thread(void*);

int main(void)
{
	pthread_t id;

        pthread_mutex_init(&mutex, NULL);       /* init mutex, default attributes */
	pthread_create(&id, NULL, &my_thread, NULL); 

	while(count < MAX_BUFFER) {
                pthread_mutex_lock(&mutex);     /* enter critical region */
		if (count < MAX_BUFFER) {
			buffer[count] = count;
			count++;
		}
                pthread_mutex_unlock(&mutex);   /* exit critical region */
	}

       	pthread_exit(NULL);
}

void *my_thread(void *p)
{
	while(count < MAX_BUFFER) {
		pthread_mutex_lock(&mutex);
		if (count < MAX_BUFFER) {
			buffer[count] = count;
			count++;
		}
		pthread_mutex_unlock(&mutex);
	}
	
	pthread_exit(NULL);
}
question Run the code above several times with the mutex protection and without protection. At the end of the program print the value of count the the contents of the array. What difference do you observe between the protected version of the program and the unprotected one?

Conditions

A condition (short for ``condition variable'') is a synchronization device that allows threads to suspend execution and relinquish the processors until some predicate on shared data is satisfied. The basic operations on conditions are: signal the condition (when the predicate becomes true), and wait for the condition, suspending the thread execution until another thread signals the condition.

A condition variable must always be associated with a mutex, to avoid the race condition where a thread prepares to wait on a condition variable and another thread signals the condition just before the first thread actually waits on it.

A condition variable is defined as pthread_cond_t.

Some of the functions that operate on conditions are described below briefly. For more detailed information see the pthread_cond_wait(3) manual page.

The example below, taken from the pthread_cond_wait(3) manual page, shows the use of conditions in POSIX threads:

pthread_mutex_t mutex;
pthread_cond_t cond;

int x=0, y=0;

int main(void)
{
	pthread_t id;

	/* initialize condition and its mutex */
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);

        pthread_create(&id, NULL, &my_thread, NULL);

        pthread_mutex_lock(&mutex);
        while (x <= y) {
                pthread_cond_wait(&cond, &mutex);     /* wait for condition */
        }

        /* so something with x and y ... */

        pthread_mutex_unlock(&mutex);

	/* keep working... */

	pthread_exit(NULL);
}

static void *my_thread(void *p)
{
	/* do something ... */

        pthread_mutex_lock(&mutex);

        /* modify x and y */

        if (x > y) 
                pthread_cond_broadcast(&cond);       /* signal condition */

        pthread_mutex_unlock(&mutex);

	/* do something else... */

	pthread_exit(NULL);
}

Resources

HTML Getting Started with POSIX Threads, by Tom Wagner and Don Towsley, Department of Computer Science, University of Massachusetts at Amherst
HTML Posix Threads Programming, Lawrence Livermore National Laboratory

Valid
	XHTML 1.0!   Powered by RedHat Linux