POSIX thread (pthread) libraries
The POSIX thread libraries are a standards based thread API for C/C++. It allows one to spawn a new concurrent process flow. It is most effective on multi-processor or multi-core systems where the process flow can be scheduled to run on another processor thus
gaining speed through parallel or distributed processing. Threads require less overhead than "forking" or spawning a new process because the system does not initialize a new system virtual memory space and environment for the process. While most effective
on a multiprocessor system, gains are also found on uniprocessor systems which exploit latency in I/O and other system functions which may halt process execution. (One thread may execute while another is waiting for I/O or some other system latency.) Parallel
programming technologies such as MPI and PVM are used in a distributed computing environment while threads are limited to a single computer system. All threads within a process share the same address space. A thread is spawned by defining a function and its
arguments which will be processed in the thread. The purpose of using the POSIX thread library in your software is to execute software faster.
Thread Basics:
- Thread operations include thread creation, termination, synchronization (joins,blocking), scheduling, data management and process interaction.
- A thread does not maintain a list of created threads, nor does it know the thread that created it.
- All threads within a process share the same address space.
- Threads in the same process share:
- Process instructions
- Most data
- open files (descriptors)
- signals and signal handlers
- current working directory
- User and group id
- Each thread has a unique:
- Thread ID
- set of registers, stack pointer
- stack for local variables, return addresses
- signal mask
- priority
- Return value: errno
- pthread functions return "0" if OK.
Thread Creation and Termination:
Example: pthread1.c
05 |
void *print_message_function( void *ptr
); |
09 |
pthread_t
thread1, thread2; |
10 |
const char *message1
= "Thread
1" ; |
11 |
const char *message2
= "Thread
2" ; |
16 |
iret1
= pthread_create( &thread1, NULL, print_message_function, ( void *)
message1); |
19 |
fprintf (stderr, "Error
- pthread_create() return code: %d\n" ,iret1); |
23 |
iret2
= pthread_create( &thread2, NULL, print_message_function, ( void *)
message2); |
26 |
fprintf (stderr, "Error
- pthread_create() return code: %d\n" ,iret2); |
30 |
printf ( "pthread_create()
for thread 1 returns: %d\n" ,iret1); |
31 |
printf ( "pthread_create()
for thread 2 returns: %d\n" ,iret2); |
37 |
pthread_join(
thread1, NULL); |
38 |
pthread_join(
thread2, NULL); |
43 |
void *print_message_function( void *ptr
) |
46 |
message
= ( char *)
ptr; |
47 |
printf ( "%s
\n" ,
message); |
Compile:
- C compiler: cc -pthread pthread1.c (or cc -lpthread pthread1.c)
or - C++ compiler: g++ -pthread pthread1.c (or g++ -lpthread pthread1.c)
The GNU compiler now has the command line option "-pthread" while older versions of the compiler specify the pthread library explicitly
with "-lpthread".
Run: ./a.out
Results:
Thread 1
Thread 2
Thread 1 returns: 0
Thread 2 returns: 0
Details:
- In this example the same function is used in each thread. The arguments are different. The functions need not be the same.
- Threads terminate by explicitly calling pthread_exit(), by letting the function return, or by a call to the function exit() which will terminate the process including any threads.
- Function call: pthread_create -
create a new thread
int pthread_create(pthread_t * thread,
const pthread_attr_t * attr,
void * (*start_routine)(void *),
void *arg);
Arguments:
- thread - returns the thread id. (unsigned long int defined in bits/pthreadtypes.h)
- attr - Set to NULL if default thread attributes are used. (else define members of the struct pthread_attr_t defined in bits/pthreadtypes.h) Attributes include:
- detached state (joinable? Default: PTHREAD_CREATE_JOINABLE. Other option: PTHREAD_CREATE_DETACHED)
- scheduling policy (real-time? PTHREAD_INHERIT_SCHED,PTHREAD_EXPLICIT_SCHED,SCHED_OTHER)
- scheduling parameter
- inheritsched attribute (Default: PTHREAD_EXPLICIT_SCHED Inherit from parent thread: PTHREAD_INHERIT_SCHED)
- scope (Kernel threads: PTHREAD_SCOPE_SYSTEM User threads: PTHREAD_SCOPE_PROCESS Pick one or the other not both.)
- guard size
- stack address (See unistd.h and bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR)
- stack size (default minimum PTHREAD_STACK_SIZE set in pthread.h),
- void * (*start_routine) - pointer to the function to be threaded. Function has a single argument: pointer to void.
- *arg - pointer to argument of function. To pass multiple arguments, send a pointer to a structure.
- Function call: pthread_join - wait
for termination of another thread
int pthread_join(pthread_t th, void **thread_return);
Arguments:
- th - thread suspended until the thread identified by th terminates, either by calling pthread_exit() or by being cancelled.
- thread_return - If thread_return is not NULL, the return value of th is stored in the location pointed to by thread_return.
- Function call: pthread_exit - terminate
the calling thread
void pthread_exit(void *retval);
Arguments:
- retval - Return value of pthread_exit().
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.
- [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:
1 |
void print_message_function( void *ptr
); |
4 |
iret1
= pthread_create( &thread1, NULL, ( void *)&print_message_function,
( void *)
message1); |
Thread Synchronization:
The threads library provides three synchronization mechanisms:
- mutexes - Mutual exclusion lock: Block access to variables by other threads. This enforces exclusive access by a thread to a variable or set of variables.
- joins - Make a thread wait till others are complete (terminated).
- condition variables - data type pthread_cond_t
Mutexes:
Mutexes are used to prevent data inconsistencies due to operations by multiple threads upon the same memory area performed at the same
time or to prevent race conditions where an order of operation upon the memory is expected. A contention or race condition often occurs when two or more threads need to perform operations on the same memory area, but the results of computations depends on
the order in which these operations are performed. Mutexes are used for serializing shared resources such as memory. Anytime a global resource is accessed by more than one thread the resource should have a Mutex associated with it. One can apply a mutex to
protect a segment of memory ("critical region") from other threads. Mutexes can be applied only to threads in a single process and do not work between processes as do semaphores.
Example threaded function:
Without Mutex |
With Mutex |
|
02 |
pthread_mutex_t
mutex1 = PTHREAD_MUTEX_INITIALIZER; |
08 |
pthread_mutex_lock(
&mutex1 ); |
10 |
pthread_mutex_unlock(
&mutex1 ); |
|
Possible execution sequence |
Thread 1 |
Thread 2 |
Thread 1 |
Thread 2 |
counter = 0 |
counter = 0 |
counter = 0 |
counter = 0 |
counter = 1 |
counter = 1 |
counter = 1 |
Thread 2 locked out.
Thread 1 has exclusive use of variable counter |
|
|
|
counter = 2 |
If register load and store operations for the incrementing of variable counter occurs with unfortunate timing, it is theoretically possible to have each thread increment and overwrite the same variable with the same value. Another possibility
is that thread two would first increment counterlocking out thread one until complete and then thread one would increment it to 2.
Sequence |
Thread 1 |
Thread 2 |
1 |
counter = 0 |
counter=0 |
2 |
Thread 1 locked out.
Thread 2 has exclusive use of variable counter |
counter = 1 |
3 |
counter = 2 |
|
Code listing: mutex1.c
06 |
pthread_mutex_t
mutex1 = PTHREAD_MUTEX_INITIALIZER; |
12 |
pthread_t
thread1, thread2; |
16 |
if (
(rc1=pthread_create( &thread1, NULL, &functionC, NULL)) ) |
18 |
printf ( "Thread
creation failed: %d\n" ,
rc1); |
21 |
if (
(rc2=pthread_create( &thread2, NULL, &functionC, NULL)) ) |
23 |
printf ( "Thread
creation failed: %d\n" ,
rc2); |
30 |
pthread_join(
thread1, NULL); |
31 |
pthread_join(
thread2, NULL); |
38 |
pthread_mutex_lock(
&mutex1 ); |
40 |
printf ( "Counter
value: %d\n" ,counter); |
41 |
pthread_mutex_unlock(
&mutex1 ); |
Compile: cc -pthread mutex1.c (or cc -lpthread mutex1.c for older versions of the GNU compiler which explicitly reference the library)
Run: ./a.out
Results:
Counter value: 1
Counter value: 2
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.
Man Pages:
- pthread_mutex_lock() - acquire a lock on the specified mutex variable.
If the mutex is already locked by another thread, this call will block the calling thread until the mutex is unlocked.
- pthread_mutex_unlock() - unlock a mutex variable. An error is
returned if mutex is already unlocked or owned by another thread.
- pthread_mutex_trylock() - attempt to lock a mutex or will return
error code if busy. Useful for preventing deadlock conditions.
Joins:
A join is performed when one wants to wait for a thread to finish. A thread calling routine may launch multiple threads then wait for them
to finish to get the results. One waits for the completion of the threads with a join.
Sample code: join1.c
05 |
void *thread_function( void *); |
06 |
pthread_mutex_t
mutex1 = PTHREAD_MUTEX_INITIALIZER; |
11 |
pthread_t
thread_id[NTHREADS]; |
14 |
for (i=0;
i < NTHREADS; i++) |
16 |
pthread_create(
&thread_id[i], NULL, thread_function, NULL ); |
19 |
for (j=0;
j < NTHREADS; j++) |
21 |
pthread_join(
thread_id[j], NULL); |
28 |
printf ( "Final
counter value: %d\n" ,
counter); |
31 |
void *thread_function( void *dummyPtr) |
33 |
printf ( "Thread
number %ld\n" ,
pthread_self()); |
34 |
pthread_mutex_lock(
&mutex1 ); |
36 |
pthread_mutex_unlock(
&mutex1 ); |
Compile: cc -pthread join1.c (or cc -lpthread join1.c for older versions of the GNU compiler which explicitly reference the library)
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
Man Pages:
Condition Variables:
A condition variable is a variable of type pthread_cond_t and is used with the appropriate functions for waiting and later, process continuation. The condition variable mechanism allows threads to suspend execution and relinquish the processor
until some condition is true. A condition variable must always be associated with a mutex to avoid a race condition created by one thread preparing to wait and another thread which may signal the condition before the first thread actually waits on it resulting
in a deadlock. The thread will be perpetually waiting for a signal that is never sent. Any mutex can be used, there is no explicit link between the mutex and the condition variable.
Man pages of functions used in conjunction with the condition variable:
- Creating/Destroying:
- Waiting on condition:
- Waking thread based on condition:
Example code: cond1.c
05 |
pthread_mutex_t
count_mutex = PTHREAD_MUTEX_INITIALIZER; |
06 |
pthread_cond_t
condition_var = PTHREAD_COND_INITIALIZER; |
08 |
void *functionCount1(); |
09 |
void *functionCount2(); |
17 |
pthread_t
thread1, thread2; |
19 |
pthread_create(
&thread1, NULL, &functionCount1, NULL); |
20 |
pthread_create(
&thread2, NULL, &functionCount2, NULL); |
22 |
pthread_join(
thread1, NULL); |
23 |
pthread_join(
thread2, NULL); |
25 |
printf ( "Final
count: %d\n" ,count); |
32 |
void *functionCount1() |
37 |
pthread_mutex_lock(
&count_mutex ); |
41 |
pthread_cond_wait(
&condition_var, &count_mutex ); |
43 |
printf ( "Counter
value functionCount1: %d\n" ,count); |
45 |
pthread_mutex_unlock(
&count_mutex ); |
47 |
if (count
>= COUNT_DONE) return (NULL); |
53 |
void *functionCount2() |
57 |
pthread_mutex_lock(
&count_mutex ); |
59 |
if (
count < COUNT_HALT1 || count > COUNT_HALT2 ) |
64 |
pthread_cond_signal(
&condition_var ); |
69 |
printf ( "Counter
value functionCount2: %d\n" ,count); |
72 |
pthread_mutex_unlock(
&count_mutex ); |
74 |
if (count
>= COUNT_DONE) return (NULL); |
Compile: cc -pthread cond1.c (or cc -lpthread cond1.c for older versions of the GNU compiler which explicitly reference the library)
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
Final count: 10
Note that functionCount1() was halted while count was between the values COUNT_HALT1 and COUNT_HALT2. The only thing that has been ensures 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 insure 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.
Thread Scheduling:
When this option is enabled, each thread may have its own scheduling properties. Scheduling attributes may be specified:
- during thread creation
- by dynamically by changing the attributes of a thread already created
- by defining the effect of a mutex on the thread's scheduling when creating a mutex
- by dynamically changing the scheduling of a thread during synchronization operations.
The threads library provides default values that are sufficient for most cases.
Thread Pitfalls:
Thread Debugging:
Thread Man Pages:
Links:
News Groups:
- comp.programming.threads
- comp.unix.solaris