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).
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.
![]() |
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.
![]() |
In the last example, what would have happened if the
threads didn't share the same data segment (such as with
fork() )? |
![]() |
Think of examples in which threads can be useful. |
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.
![]() |
Provide a solution for the "element "
problem in the last example |
![]() |
What is wrong with the following code?
Provide a correct alternative to the code above.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); ... } |
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.
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.
pthread_mutex_init(mutex, attr)
,
initializes a mutex variable.pthread_mutex_lock(mutex)
, tries to lock a
mutex. On success, the mutex is locked and the thread
continues its execution. On failure, the thread is suspended
until the lock is released.pthread_mutex_unlock(mutex)
, unlocks a mutex
and wakes up a suspended process (if any) waiting on the same
mutex.pthread_mutex_destroy(mutex)
, destroys a
mutex object.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); }
![]() |
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? |
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.
pthread_cond_init(cond,
attribute)
, initialize a condition variable
using the attributes in
attribute, or default attributes if attribute is
NULL.
pthread_cond_wait(cond,
mutex)
, automatically unlocks the mutex and
waits for the condition variable to be signaled.pthread_cond_signal(cond)
restarts one of the threads that are waiting on the condition
variable cond.pthread_cond_broadcast(cond)
restarts
all the threads that are waiting on the condition variable
cond.
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); }
![]() |
Getting Started with POSIX Threads, by Tom Wagner and Don Towsley, Department of Computer Science, University of Massachusetts at Amherst |
![]() |
Posix Threads Programming, Lawrence Livermore National Laboratory |