0% found this document useful (0 votes)
100 views15 pages

Ecole Militaire Polytechnique: Content

This document provides a guide to multi-threaded programming with POSIX threads (pthreads). It discusses key concepts like threads, mutexes, and condition variables. Threads allow parallel execution and faster context switching than processes. Mutexes are used to synchronize access to shared memory between threads to avoid race conditions. Condition variables allow threads to wait until a certain condition occurs before resuming execution. The guide provides examples of how to create and manage threads, use mutexes and condition variables to synchronize access to shared resources, and properly terminate threads.

Uploaded by

Tarak Kai
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
100 views15 pages

Ecole Militaire Polytechnique: Content

This document provides a guide to multi-threaded programming with POSIX threads (pthreads). It discusses key concepts like threads, mutexes, and condition variables. Threads allow parallel execution and faster context switching than processes. Mutexes are used to synchronize access to shared memory between threads to avoid race conditions. Condition variables allow threads to wait until a certain condition occurs before resuming execution. The guide provides examples of how to create and manage threads, use mutexes and condition variables to synchronize access to shared resources, and properly terminate threads.

Uploaded by

Tarak Kai
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 15

Ecole Militaire Polytechnique

Chahid Abderrahmane Taleb

Guide de programmation multithread avec PThread

Content
● What Is a Thread? Why Use Threads?
● Creating And Destroying Threads
● Synchronizing Threads With Mutexes
○ What Is A Mutex?
○ Creating And Initializing A Mutex
○ Locking And Unlocking A Mutex
○ Destroying A Mutex
○ Using A Mutex - A Complete Example
○ Starvation And Deadlock Situations
● Refined Synchronization - Condition Variables
○ What Is A Condition Variable?
○ Creating And Initializing A Condition Variable
○ Signaling A Condition Variable
○ Waiting On A Condition Variable
○ Destroying A Condition Variable
● Thread Cancellation And Termination
○ Canceling A Thread
○ Setting Thread Cancellation State
○ Cancellation Points
○ Setting Thread Cleanup Functions
○ Synchronizing On Threads Exiting
○ Detaching A Thread
This tutorial is an attempt to help you become familiar with multi-threaded programming
with the POSIX threads (pthreads) library, and to show how its features can be used in
"real-life" programs. It explains the different tools defined by the library, and shows how to
use them.

When talking about POSIX threads, one cannot avoid the question "Which draft of the
POSIX threads standard shall be used?". As this thread's standard has been revised over
a period of several years, one will find that implementations adhering to different drafts of
the standard have a different set of functions, different default values, and different
nuances.

What Is a Thread? Why Use Threads


A thread is a semi-process that has its own stack, and executes a given piece of code.
Unlike a real process, the thread normally shares its memory with other threads (whereas
for processes we usually have a different memory area for each). A Thread Group is a set
of threads all executing inside the same process. They all share the same memory, and
thus can access the same global variables, same heap memory, same set of file
descriptors, etc. All these threads execute in parallel (i.e. using time slices, or if the
system has several processors, then really in parallel).

The advantage of using a thread group instead of a normal serial program is that several
operations may be carried out in parallel, and thus events can be handled immediately as
they arrive .

The advantage of using a thread group over a process group is that context switching
between threads is much faster then context switching between processes. Also,
communications between threads is usually faster and easier to implement than
communications between processes.

On the other hand, because threads in a group all use the same memory space, if one
among them corrupts the contents of its memory, other threads might suffer as well.
With processes, the operating system protects processes from one another, and
thus if one corrupts its own memory space, other processes won't be affected. Another
advantage of using processes is that they can run on different machines, while all
the threads have to run on the same machine.

Creating And Destroying Threads


Reference

When a multi-threaded program starts executing, it has one thread running, which
executes the main() function of the program. This is already a full-fledged thread, with its
own thread ID. In order to create a new thread, the program should use the
pthread_create() function. Here is how to use it:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *print_message_function( void *ptr );


void main()
{
pthread_t thread1, thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";
int iret1, iret2;

/* Create independent threads each of which will execute function */


iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);

/* Wait till threads are complete before main continues. Unless we */


/* wait we run the risk of executing an exit which will terminate */
/* the process and all threads before the threads have completed. */
//pthread_join( thread1, NULL);
//pthread_join( thread2, NULL);

printf("Thread 1 returns: %d\n",iret1);


printf("Thread 2 returns: %d\n",iret2);
exit(0);
}
void *print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s \n", message);
//pthread_exit(0);
}

Arguments:
a. thread - returns the thread id. (unsigned long int defined in
bits/pthreadtypes.h)
b. attr - Set to NULL if default thread attributes are used.
c. void * (*start_routine) - pointer to the function to be threaded. Function has
a single argument: pointer to void.
d. *arg - pointer to argument of function. To pass multiple arguments, send a
pointer to a structure.
2. Function call: void pthread_exit(void *retval)
Arguments:
a. retval - Return value of thread.
3. This routine kills the thread. The pthread_exit function never returns. If the thread is
not detached, the thread id and return value may be examined from another thread
by using pthread_join.
Note: the return pointer *retval, must not be of local scope otherwise it would cease
to exist once the thread terminates.
4. [C++ pitfalls]: The above sample program will compile with the GNU C and C++
compiler g++. The following function pointer representation below will work for C
but not C++. Note the subtle differences and avoid the pitfall below:
void print_message_function( void *ptr );

In order to compile a multi-threaded program using gcc, we need to link it with the
pthreads library. Assuming you have this library already installed on your system, here is
how to compile our first program:

g++/gcc TP0.cpp -lpthread -o TP0 && ./TP0


Synchronizing Threads With Mutexes
One of the basic problems when running several threads that use the same memory
space, is making sure they don't "step on each other's toes". By this we refer to the
problem of using a data structure from two different threads.

For instance, consider the case where two threads try to update two variables. One tries to
set both to 0, and the other tries to set both to 1. If both threads try to do that at the same
time, we might get into a situation where one variable contains 1, and one contains 0. This
is because a context-switch (we already know what this is by now, right?) might occur after
the first thread zeroed out the first variable, then the second thread would set both
variables to 1, and when the first thread resumes operation, it will zero out the second
variable, thus getting the first variable set to '1', and the second set to '0'.

What Is A Mutex?
A basic mechanism supplied by the pthreads library to solve this problem, is called a
mutex. A mutex is a lock that guarantees three things:

1. Atomicity - Locking a mutex is an atomic operation, meaning that the operating


system (or threads library) assures you that if you locked a mutex, no other thread
succeeded in locking this mutex at the same time.
2. Singularity - If a thread manages to lock a mutex, it is assured that no other thread
will be able to lock the thread until the original thread releases the lock.
3. Non-Busy Wait - If a thread attempts to lock a thread that was locked by a second
thread, the first thread will be suspended (and will not consume any CPU
resources) until the lock is freed by the second thread. At this time, the first thread
will wake up and continue execution, having the mutex locked by it.

From these three points we can see how a mutex can be used to assure exclusive
access to variables (or in general critical code sections). Here is some pseudo-code that
updates the two variables we were talking about in the previous section, and can be used
by the first thread:

lock mutex 'X1'.


set first variable to '0'.
set second variable to '0'.
unlock mutex 'X1'.

Meanwhile, the second thread will do something like this:

lock mutex 'X1'.


set first variable to '1'.
set second variable to '1'.
unlock mutex 'X1'.

Assuming both threads use the same mutex, we are assured that after they both run
through this code, either both variables are set to '0', or both are set to '1'. You'd note this
requires some work from the programmer - If a third thread was to access these
variables via some code that does not use this mutex, it still might mess up the variable's
contents. Thus, it is important to enclose all the code that accesses these variables in a
small set of functions, and always use only these functions to access these variables.

Thread synchronization with Join:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *functionC();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;

int main()
{
int rc1, rc2,rc3, rc4;
pthread_t thread1, thread2,thread3, thread4;
/* Create independent threads each of which will execute functionC */
if( (rc1=pthread_create( &thread1, NULL, &functionC, NULL)) )
printf("Thread creation failed: %d\n", rc1);
if( (rc2=pthread_create( &thread2, NULL, &functionC, NULL)) )
printf("Thread creation failed: %d\n", rc2);
if( (rc3=pthread_create( &thread3, NULL, &functionC, NULL)) )
printf("Thread creation failed: %d\n", rc3);
if( (rc4=pthread_create( &thread4, NULL, &functionC, NULL)) )
printf("Thread creation failed: %d\n", rc4);
/* Wait till threads are complete before main continues. Unless we */
/* wait we run the risk of executing an exit which will terminate */
/* the process and all threads before the threads have completed. */
pthread_join( thread1, NULL);
pthread_join( thread2, NULL);
pthread_join( thread3, NULL);
pthread_join( thread4, NULL);
exit(0);
}
void *functionC()
{
//pthread_mutex_lock( &mutex1 );
counter++;
printf("Counter value: %d\n",counter);
//pthread_mutex_unlock( &mutex1 );
}

Compile:
Compile: cc -lpthread join1.c
Run: ./a.out
Results:
Thread number 1026
Thread number 2051
Thread number 3076
Thread number 4101
Thread number 5126
Thread number 6151
Thread number 7176
Thread number 8201
Thread number 9226
Thread number 10251
Final counter value: 10

Thread synchronization with Mutex:

#include <stdio.h>
#include <pthread.h>
#define NTHREADS 10
void *thread_function(void *);
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;

main()
{
pthread_t thread_id[NTHREADS];
int i, j;

for(i=0; i < NTHREADS; i++)


{
pthread_create( &thread_id[i], NULL, thread_function, NULL );
}

for(j=0; j < NTHREADS; j++)


{
pthread_join( thread_id[j], NULL);
}

/* Now that all threads are complete I can print the final result. */
/* Without the join I could be printing a value before all the threads */
/* have been completed. */

printf("Final counter value: %d\n", counter);


}

void *thread_function(void *dummyPtr)


{
printf("Thread number %ld\n", pthread_self());
pthread_mutex_lock( &mutex1 );
counter++;
pthread_mutex_unlock( &mutex1 );
}

Compile:

Compile: cc -lpthread mutex1.c


Run: ./a.out
Results:

Counter value: 1
Counter value: 2
Note:

When a mutex lock is attempted against a mutex which is held by another thread, the
thread is blocked until the mutex is unlocked. When a thread terminates, the mutex does
not unless explicitly unlocked. Nothing happens by default.

Starvation And Deadlock Situations


Again we should remember that pthread_mutex_lock() might block for a
non-determined duration, in case of the mutex being already locked. If it remains locked
forever, it is said that our poor thread is "starved" - it was trying to acquire a resource, but
never got it. It is up to the programmer to ensure that such starvation won't occur. The
pthread library does not help us with that.

The pthread library might, however, figure out a "deadlock". A deadlock is a situation in
which a set of threads are all waiting for resources taken by other threads, all in the same
set. Naturally, if all threads are blocked waiting for a mutex, none of them will ever
come back to life again. The pthread library keeps track of such situations, and thus
would fail the last thread trying to call pthread_mutex_lock(), with an error of type
EDEADLK. The programmer should check for such a value, and take steps to solve the
deadlock somehow.

Refined Synchronization - Condition Variables


As we've seen before with mutexes, they allow for simple coordination - exclusive access
to a resource. However, we often need to be able to make real synchronization between
threads:

● In a server, one thread reads requests from clients, and dispatches them to several
threads for handling. These threads need to be notified when there is data to
process, otherwise they should wait without consuming CPU time.
● In a GUI (Graphical User Interface) Application, one thread reads user input,
another handles graphical output, and a third thread sends requests to a server
and handles its replies. The server-handling thread needs to be able to notify the
graphics-drawing thread when a reply from the server arrives, so it will immediately
show it to the user. The user-input thread needs to be always responsive to the
user, for example, to allow her to cancel long operations currently executed by the
server-handling thread.

All these examples require the ability to send notifications between threads. This is where
condition variables are brought into the picture.

What Is A Condition Variable?


A condition variable is a mechanism that allows threads to wait (without wasting CPU
cycles) for some event to occur. Several threads may wait on a condition variable, until
some other thread signals this condition variable (thus sending a notification). At this
time, one of the threads waiting on this condition variable wakes up, and can act on the
event. It is possible to also wake up all threads waiting on this condition variable by using a
broadcast method on this variable.

Note that a condition variable does not provide locking. Thus, a mutex is used along
with the condition variable, to provide the necessary locking when accessing this
condition variable.

Creating And Initializing A Condition Variable


Creation of a condition variable requires defining a variable of type pthread_cond_t, and
initializing it properly. Initialization may be done with either a simple use of a macro named
PTHREAD_COND_INITIALIZER or the usage of the pthread_cond_init() function.
We will show the first form here:

pthread_cond_t got_request = PTHREAD_COND_INITIALIZER;


This defines a condition variable named 'got_request', and initializes it.

Note:

● since the PTHREAD_COND_INITIALIZER is actually a structure, it may be used to


initialize a condition variable only when it is declared.
● In order to initialize it during runtime, one must use the pthread_cond_init()
function.

Signaling A Condition Variable


● In order to signal a condition variable, one should use either the
pthread_cond_signal() function (to awaken up only one thread waiting on this
variable),
● or the pthread_cond_broadcast() function (to wake up all threads waiting on
this variable).

Here is an example using signal, assuming 'got_request' is a properly initialized


condition variable:

int rc = pthread_cond_signal(&got_request);

Or by using the broadcast function:

int rc = pthread_cond_broadcast(&got_request);

When either function returns, 'rc' is set to 0 on success, and to a non-zero value on
failure. In such a case (failure), the return value denotes the error that occured.

Note:

● Success of a signaling operation does not mean any thread was awakened - it
might be that no thread was waiting on the condition variable, and thus the
signaling does nothing (i.e. the signal is lost).
● It is also not remembered for future use - if after the signaling function returns
another thread starts waiting on this condition variable, a further signal is required
to wake it up.

Waiting On A Condition Variable


If one thread signals the condition variable, other threads would probably want to wait for
this signal. They may do so using one of two functions, pthread_cond_wait() or
pthread_cond_timedwait(). Each of these functions takes a condition variable, and a
mutex (which should be locked before calling the wait function), unlocks the mutex, and
waits until the condition variable is signaled, suspending the thread's execution.

The only difference between these two functions is that pthread_cond_timedwait()


allows the programmer to specify a timeout for the waiting, after which the function always
returns, with a proper error value (ETIMEDOUT) to notify that condition variable was NOT
signaled before the timeout passed. The pthread_cond_wait() would wait indefinitely
if it was never signaled.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;


pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condition_cond = PTHREAD_COND_INITIALIZER;

void *functionCount1();
void *functionCount2();
int count = 0;
#define COUNT_DONE 10
#define COUNT_HALT1 3
#define COUNT_HALT2 6

main()
{
pthread_t thread1, thread2;
pthread_create( &thread1, NULL, &functionCount1, NULL);
pthread_create( &thread2, NULL, &functionCount2, NULL);
pthread_join( thread1, NULL);
pthread_join( thread2, NULL);
exit(0);
}
void *functionCount1()
{
for(;;)
{
pthread_mutex_lock( &condition_mutex );
while( count >= COUNT_HALT1 && count <= COUNT_HALT2 )
{
pthread_cond_wait( &condition_cond, &condition_mutex );
}
pthread_mutex_unlock( &condition_mutex );
pthread_mutex_lock( &count_mutex );
count++;
printf("Counter value functionCount1: %d\n",count);
pthread_mutex_unlock( &count_mutex );

if(count >= COUNT_DONE) return(NULL);


}
}

void *functionCount2()
{
for(;;)
{
pthread_mutex_lock( &condition_mutex );
if( count < COUNT_HALT1 || count > COUNT_HALT2 )
{
pthread_cond_signal( &condition_cond );
}
pthread_mutex_unlock( &condition_mutex );

pthread_mutex_lock( &count_mutex );
count++;
printf("Counter value functionCount2: %d\n",count);
pthread_mutex_unlock( &count_mutex );
if(count >= COUNT_DONE) return(NULL);
}

Compile:

Compile: cc -lpthread cond1.c


Run: ./a.out

Results:
Counter value functionCount1: 1
Counter value functionCount1: 2
Counter value functionCount1: 3
Counter value functionCount2: 4
Counter value functionCount2: 5
Counter value functionCount2: 6
Counter value functionCount2: 7
Counter value functionCount1: 8
Counter value functionCount1: 9
Counter value functionCount1: 10
Counter value functionCount2: 11

Note:

Note that functionCount1() was halted while count was between the values
COUNT_HALT1 and COUNT_HALT2. The only thing that has been ensured is that
functionCount2 will increment the count between the values COUNT_HALT1 and
COUNT_HALT2. Everything else is random.
The logic conditions (the "if" and "while" statements) must be chosen to ensure that the
"signal" is executed if the "wait" is ever processed. Poor software logic can also lead to a
deadlock condition.
Note: Race conditions abound with this example because count is used as the condition
and can't be locked in the while statement without causing deadlock. I'll work on a cleaner
example but it is an example of a condition variable.

Functions used in conjunction with the condition variable:

● Creating/Destroying:

○ pthread_cond_init
○ pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
○ pthread_cond_destroy
● Waiting on condition:
○ pthread_cond_wait
○ pthread_cond_timedwait - place limit on how long it will block.
● Waking thread based on condition:
○ pthread_cond_signal
○ pthread_cond_broadcast - wake up all threads blocked by the
specified condition variable.

Destroying A Condition Variable


After we are done using a condition variable, we should destroy it, to free any system
resources it might be using. This can be done using the pthread_cond_destroy(). In
order for this to work, there should be no threads waiting on this condition variable. Here is
how to use this function, again, assuming 'got_request' is a pre-initialized condition
variable:

int rc = pthread_cond_destroy(&got_request);
if (rc == EBUSY) {
/* some thread is still waiting on this condition variable */
/* handle this case here... */
.
.
}

Thread Cancellation And Termination


Reference

As we create threads, we need to think about terminating them as well. There are several
issues involved here. We need to be able to terminate threads cleanly. Unlike processes,
where a very ugly method of using signals is used, the folks that designed the pthreads
library were a little more thoughtful. So they supplied us with a whole system of canceling
a thread, cleaning up after a thread, and so on. We will discuss these methods here.

Canceling A Thread

When we want to terminate a thread, we can use the pthread_cancel function. This
function gets a thread ID as a parameter, and sends a cancellation request to this thread.
What this thread does with this request depends on its state. It might act on it immediately,
it might act on it when it gets to a cancellation point (discussed below), or it might
completely ignore it. We'll see later how to set the state of a thread and define how it acts
on cancellation requests. Lets first see how to use the cancel function. We assume that
'thr_id' is a variable of type pthread_id containing the ID of a running thread:

pthread_cancel(thr_id);

The pthread_cancel() function returns 0, so we cannot know if it succeeded or not.

Setting Thread Cancellation State

A thread's cancel state may be modified using several methods. The first is by using the
pthread_setcancelstate() function. This function defines whether the thread will
accept cancellation requests or not. The function takes two arguments. One that sets the
new cancel state, and one into which the previous cancel state is stored by the function.
Here is how it is used:
int old_cancel_state;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state);
/*This will disable canceling this thread. We can also enable canceling
the thread like this:*/
int old_cancel_state;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancel_state);

Note that you may supply a NULL pointer as the second parameter, and then you won't get
the old cancel state.

A similar function, named pthread_setcanceltype() is used to define how a thread


responds to a cancellation request, assuming it is in the 'ENABLED' cancel state. One
option is to handle the request immediately (asynchronously). The other is to defer the
request until a cancellation point. To set the first option (asynchronous cancellation), do
something like:

int old_cancel_type;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_cancel_type);
/*And to set the second option (deferred cancellation):*/
int old_cancel_type;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_cancel_type);

Note that you may supply a NULL pointer as the second parameter, and then you won't get
the old cancel type.

You might wonder - "What if I never set the cancellation state or type of a thread?".
Well, in such a case, the pthread_create() function automatically sets the thread to
enabled deferred cancellation, that is, PTHREAD_CANCEL_ENABLE for the cancel mode,
and PTHREAD_CANCEL_DEFERRED for the cancel type.

Cancellation Points

As we've seen, a thread might be in a state where it does not handle cancel requests
immediately, but rather defers them until it reaches a cancellation point. So what are these
cancellation points?

In general, any function that might suspend the execution of a thread for a long time,
should be a cancellation point. In practice, this depends on the specific implementation,
and how conformant it is to the relevant POSIX standard (and which version of the
standard it conforms to...). The following set of pthread functions serve as cancellation
points:

● pthread_join()
● pthread_cond_wait()
● pthread_cond_timedwait()
● pthread_testcancel()
● sem_wait()
● sigwait()

This means that if a thread executes any of these functions, it'll check for deferred cancel
requests. If there is one, it will execute the cancellation sequence, and terminate. Out of
these functions, pthread_testcancel() is unique - it's only purpose is to test whether a
cancellation request is pending for this thread. If there is, it executes the cancellation
sequence. If not, it returns immediately. This function may be used in a thread that does a
lot of processing without getting into a "natural" cancellation state.

Setting Thread Cleanup Functions

One of the features the pthreads library supplies is the ability for a thread to clean up after
itself, before it exits. This is done by specifying one or more functions that will be called
automatically by the pthreads library when the thread exits, either due to its own will (e.g.
calling pthread_exit()), or due to it being canceled.

Two functions are supplied for this purpose. The pthread_cleanup_push() function is
used to add a cleanup function to the set of cleanup functions for the current thread. The
pthread_cleanup_pop() function removes the last function added with
pthread_cleanup_push(). When the thread terminates, its cleanup functions are
called in the reverse order of their registration. So the last one to be registered is
the first one to be called.

When the cleanup functions are called, each one is supplied with one parameter that was
supplied as the second parameter to the pthread_cleanup_push() function call.

Synchronizing On Threads Exiting

Sometimes it is desired for a thread to wait for the end of execution of another thread. This
can be done using the pthread_join() function. It receives two parameters: a variable of
type pthread_t, denoting the thread to be joined, and an address of a void* variable, into
which the exit code of the thread will be placed (or PTHREAD_CANCELED if the joined
thread was canceled).

The pthread_join() function suspends the execution of the calling thread until the joined
thread is terminated.

For example, consider our earlier thread pool server. Looking back at the code, you'll see
that we used an odd sleep() call before terminating the process. We did this since the main
thread had no idea when the other threads finished processing all pending requests. We
could have solved it by making the main thread run a loop of checking if no more requests
are pending, but that would be a busy loop.

A cleaner way of implementing this, is by adding three changes to the code:

1. Tell the handler threads when we are done creating requests, by setting some flag.
2. Make the threads check, whenever the requests queue is empty, whether or not
new requests are supposed to be generated. If not, then the thread should exit.
3. Make the main thread wait for the end of execution of each of the threads it
spawned.

The first 2 changes are rather easy. We create a global variable named
'done_creating_requests' and set it to '0' initially. Each thread checks the contents of this
variable every time before it intends to go to wait on the condition variable (i.e. the
requests queue is empty).

The main thread is modified to set this variable to '1' after it finishes generating all
requests. Then the condition variable is being broadcast, in case any of the threads is
waiting on it, to make sure all threads go and check the 'done_creating_requests' flag.

The last change is done using a pthread_join() loop: call pthread_join() once for each
handler thread. This way, we know that only after all handler threads have exited, this loop
is finished, and then we may safely terminate the process. If we didn't use this loop, we
might terminate the process while one of the handler threads is still handling a request.
Example 1:

The program below creates a thread and then cancels it. The main thread joins with the
canceled thread to check that its exit status was PTHREAD_CANCELED. The following
shell session shows what happens when we run the program:

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
static void * thread_func(void *ignored_argument)
{
int s;
/* Disable cancellation for a while, so that we don't
immediately react to a cancellation request. */
s = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
if (s != 0)
handle_error_en(s, "pthread_setcancelstate");
printf("thread_func(): started; cancellation disabled\n");
sleep(5);
printf("thread_func(): about to enable cancellation\n");
s = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (s != 0)
handle_error_en(s, "pthread_setcancelstate");
/* sleep() is a cancellation point. */
sleep(1000); /* Should get canceled while we sleep */
/* Should never get here. */
printf("thread_func(): not canceled!\n");
return NULL;
}
int main(void)
{
pthread_t thr;
void *res;
int s;
/* Start a thread and then send it a cancellation request. */
s = pthread_create(&thr, NULL, &thread_func, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");
sleep(2); /* Give thread a chance to get started */
printf("main(): sending cancellation request\n");
s = pthread_cancel(thr);
if (s != 0)
handle_error_en(s, "pthread_cancel");
/* Join with thread to see what its exit status was. */
s = pthread_join(thr, &res);
if (s != 0)
handle_error_en(s, "pthread_join");
if (res == PTHREAD_CANCELED)
printf("main(): thread was canceled\n");
else
printf("main(): thread wasn't canceled (shouldn't happen!)\n");
exit(EXIT_SUCCESS);
}

Compile
gcc -pthread -o main main.c && ./main

Result:
thread_func(): started; cancellation disabled
main(): sending cancellation request
thread_func(): about to enable cancellation
main(): thread was canceled

Detaching A Thread
Reference
● The pthread_detach() function marks the thread identified by thread as detached.
When a detached thread terminates, its resources are automatically released back
to the system without the need for another thread to join with the terminated thread.
● Attempting to detach an already detached thread results in unspecified behavior.

pthread_t a_thread; /* store the thread's structure here */


int rc; /* return value for pthread functions. */
extern void* thread_loop(void*); /* declare the thread's main function. */
/* create the new thread. */
rc = pthread_create(&a_thread, NULL, thread_loop, NULL);
/* and if that succeeded, detach the newly created thread. */
if (rc == 0) {
rc = pthread_detach(a_thread);
}

Of-course, if we wish to have a thread in the detached state immediately, using the first
option (setting the detached state directly when calling pthread_create() is more efficient.

You might also like