2. THREADS
• A typical UNIX process can be thought of as having
a single thread of control
• With multiples threads we can design programs to
do more than one thing at a time. Benefits:
• Simplify code that deal with asynchronous events
• Easy sharing of memory and file descriptors
• Some problems can be partitioned so that overall
program throughput can be improved
• Interactive program can realise improved
response time
3. THREADS
• A thread consists of the information necessary to
represent an execution context within a process
• Include thread ID, a set of register values, a
stack, a scheduling priority and policy, a signal
mask, an errno variable and thread-specific
data
• Everything within a process is shareable among
the threads in a process, including the text of the
executable, the program’s global and heap
memory, the stacks and file descriptors
4. THREAD CREATION
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void),
void restrict *arg);
Return 0 if OK, error number on failure
• The memory location pointed to by tidp is set to the
thread ID of the newly created thread
• The attr argument is used to customize various thread
attributes (NULL for default)
• New thread starts running at the address of start_fn
5. THREAD CREATION
• start_rtn takes a single argument, arg, which is
a typeless pointer
• If more than one argument are required, a structure
needs to defined, initialised and passed
• When a thread is created there is no guarantee
which runs first
• The new thread has access to the process address
space and inherits the calling thread’s floating point
environment and signal mask
• The set of pending signals for the thread is cleared
7. THREAD TERMINATION
• If any thread within a process calls an exit function
then the entire process terminates
• When the default action is to terminate the process, a
signal sent to a thread will terminate the entire
process
• A single thread can exit in three ways:
• Return from the start routine
• Thread can be cancelled
• Thread calls pthread_exit
8. THREAD TERMINATION
#include <pthread.h>
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread,
void **rval_ptr);
Return 0 if OK, errno on failure
• When joining, the calling thread will block until the
specified thread call pthread_exit, returns from
its start routine or is cancelled
• If cancelled, memory location of rval_ptr is set to
PTHREAD_CANCELLED
10. THREAD TERMINATION
#include <pthread.h>
void pthread_cancel(pthread_t tid);
Return 0 if OK, errno on failure
int pthread_cleanup_push(
void (*rtn) (void *), void *arg);
void pthread_cleanup_pop(int execute)
• pthead_cancel doesn’t wait for thread to
terminate, it merely make the request
• Threads can choose to ignore the cancel request, or
control how it is cancelled
11. THREAD TERMINATION
• A thread can arrange for functions to be called
when it exists, similar to atexit functions
• They are known as thread cleanup handlers
• More than one handler can be established for
each thread
• Handlers are recorded in a stack, so they are
executed in reverse order from that with which
they were registered
12. THREAD DETACHING
#include <pthread.h>
void pthead_detach(pthread_t tid);
Return 0 if OK, errno if failure
• By default a thread’s termination status is retained
until pthread_join is called
• A thread’s underlying storage can be reclaimed
immediately on termination if the thread is
detached
• A call to pthread_join for a detached process
will fail, returning EINVAL
13. THREAD ATTRIBUTES
• detachstate: detached thread attribute
(joinable or detached)
• guardsize: guard buffer size in bytes at the
end of thread stack
• stackaddr: lowest address of thread stack
• stacksize: size in bytes of thread stack
• Remember: threads share the virtual address
space, where threads’ stack reside
• Default guardsize is PAGESIZE bytes
14. THREAD SYNCHRONISATION
• When multiple threads share the same memory we
need to make sure that each thread sees a
consistent view of the data
• If each thread uses variables that other threads
don’t read or modify, or variables are readonly,
no consistency problems exist
• When one thread can modify a variable that
other thread can read or modify we need to
synchronise the threads to ensure that they don’t
use an invalid value when accessing the memory’s
content
17. MUTEXES
• A mutex (mutual exclusion lock) is a lock that we
set (lock) before accessing a shared resource and
release (unlock) when we’re done
• While it is set any other thread that tries to set it
will block until we release it
• If more than one thread is blocked when we
unlock the mutex, then they all will be made
runnable and the first one to run will be able to
set the lock
• Others will see mutex still locked and go back
waiting
18. MUTEXES
#include <pthread.h>
int ptrhead_mutex_init(
pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t restrict addr);
int pthread_mutex_destroy(
pthread_mutex_t *mutex);
Return 0 if OK, errno on error
• A mutex variable is represented by the
pthread_mutex_t data type
• Mutexes must be initialised by either setting it to the
constant PRTHEAD_MUTEX_INITIALISER (for
static mutexes) or called pthread_mutex_init
19. MUTEXES
#include <pthread.h>
int pthread_mutex_lock(
pthread_mutex_t *mutex);
int pthread_mutex_trylock(
pthread_mutex_t *mutex);
int pthread_mutex_unlock(
pthread_mutex_t *mutex);
Return 0 if OK, errno on failure
• If thread can’t afford to block for a mutex to become
unlocked, pthread_mutex_trylock can be
used to lock the mutex conditionally
21. DEADLOCK AVOIDANCE
• A thread will deadlock itself if it tries to lock the
same mutex twice
• There are less obvious ways how deadlock can
occur
• They can be avoided by carefully controlling the
order in which mutexes are locked
• In case of complex program architectures, you
can try using pthread_mutex_lock to
avoid deadlocking
22. SYNCHRONISATION ATTRIBUTES
• By default, multiple threads can access the same
synchronisation objects (process-shared mutex =
PTHREAD_PROCESS_PRIVATE)
• Mechanism exist which allow independent processes
to map the same extent of memory into their
independent memory space
• If process-shared mutex =
PTHREAD_PROCESS_SHARED, a mutex
allocated from a shared memory extent may be
used for synchronisation by those processes
23. SYNCHRONISATION ATTRIBUTES
• type mutex attribute controls the characteristics of
the mutex:
• normal – standard mutex that doesn’t do any
special error checking or deadlock detection
• errorcheck – provides error checking
• recursive – allows the same thread to lock it
multiple times without first unlocking it (using
internal lock count)
• default – request default semantics
(implementation specific)
24. READERWRITER LOCKS
• RW locks allow for higher degrees of parallelism
• Three states are possible: locked in read mode, locked
in write mode, unlocked
• Only one thread at a time can hold a RW lock in write
mode, however multiple thread can hold it in read
mode at the same time
• When write-locked, all threads trying to lock it block
until unlocked
• When in read-mode, all thread trying to lock it in read
mode are given access, however threads attempting to
lock in write mode are blocked
25. READERWRITER LOCKS
• RW locks usually block additional readers if a
block is already held in read mode and a thread is
blocked trying to acquire it in write mode
• This avoids writer starvation
• RW locks are well suited for situations where data
structures are read more often than they are
modified
• RW are also called shared exclusive locks
• They must be initialised before use and destroyed
before freeing their underlying memory
26. READERWRITER LOCKS
#include <pthread.h>
int pthread_rw_lock_init(
pthread_rw_lock_t *restrict rwlock,
const pthread_rwlockattr_t attr);
int pthread_rwlock_destroy(
pthread_rwlock_t *rwlock)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_tryrdlock(
pthread_rwlock_t *rwlock)
int pthread_rwlock_trywrlock(
pthread_rwlock_t *rwlock)
Return 0 if OK, errno if failure
27. CONDITION VARIABLES
• Synchronisation mechanism for threads
• Provide a place for threads to rendevouz
• When used with mutexes, condition variables allow
threads to wait in a race-free way for arbitrary
conditions to occur
• A thread must first lock a mutex to change the
condition state
• Other thread won’t notice the change until they
acquire the mutex
• Initialised with PTHREAD_COND_INITIALIZER
28. CONDITION VARIABLES
#include <pthread.h>
int pthread_cond_init(
pthread_cond_t *restrict cond,
pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
cont struct timespec *restrict timeout
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
Return 0 if OK, errno on failure
30. BARRIERS
• Synchronisation mechanism that lets you corral
several cooperating threads, forcing them to
wait at a specific point until all have finished
before any one thread can continue
• Unlike pthread_join, you’re waiting for the
threads to rendevouz at a cerain point
• When the specified number of threads arrive at
a barrier we unlock all of them so they can
continue to run.
31. BARRIERS
#include <pthread.h>
int pthread_barrier_init(
pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr,
unsigned int count);
int pthread_barrier_destroy(
pthread_barrier_t *barrier);
int pthread_barrier_wait(
pthread_barrier_t *barrier);
Return 0 if OK, errno on error
• count holds the number of thread that must call
pthread_barrier_wait
33. THREAD-SPECIFIC DATA
• Also known as thread-private data
• A mechanism for storing and finding data
associated with a particular thread
• Each thread accesses its own separate copy of the
data without worrying about synchronising access
with other threads
• For example, this is useful to redefine errno such
that each thread has it’s own private version
• Except for using registers, there is no way for one
thread to prevent another from accessing its data.
34. THREAD-SPECIFIC DATA
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp,
void (*destructor) (void *));
int pthread_key_delete(pthread_key_t *key);
Return 0 if OK, errno on failure
• Before allocating thread-specific data we need to
create a key to associate with the data
• Same key can be used by all threads in the process,
but each thread will associate a different thread-
specific data address with the key
35. THREAD-SPECIFIC DATA
• pthread_key_create also associates an
optional destructor function with the key
• When thread exits the destructor is called with the
data address as the only argument
• Threads generally use malloc to allocate thread-
specific data, the destructor usually frees the
allocated memory
• When thread exits the destructors are called in an
implementation-defined order
• pthread_key_delete will not invoke the
destructor
36. THREAD-SPECIFIC DATA
• Depending on how the system schedules threads,
some threads might see one key value, whereas
other threads migh see a different key value
• This race can be solved by using pthread_once
• initflag must be a nonlocal variable and
initialised
#include <pthread.h>
pthread_once_t = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag,
void (*init_routine)(void));
38. THREAD-SPECIFIC DATA
• Once a key is created we can associate thread-
specific data with the key
• Once they are set, we can access the data anytime
#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
Returns thread-specific data or NULL if no
value has been associated with the key
int pthread_setspecific(pthread_key_t key,
cont void *value);
Return 0 of OK, errno on failure
39. THREADS AND SIGNALS
• Each thread has its own signal mask, but signal
disposition is shared by all threads in a process
• Individual threads can block signals, but when a
thread modifies the action associated with a signal
all threads share the action
• Signals are delivered to a single thread in the
process
• If the signal is related to a hardware fault or timer
it is sent to the thread whose action caused the
event
• Other signals are delivered arbitrarily
40. THREADS AND SIGNALS
#include <pthread.h>
int pthread_sigmask(int how,
const sigset_t *restrict set,
sigset_t *restrict oset
int sigwait(const sigset_t *restrict set,
int restrict signop);
int pthread_kill(pthread_t thread, int signo);
Return 0 if OK, errno on failure
• sigprocmask is undefined in a multithreaded
process, so pthread_signals has to be used.
• A thread can wait for one or more signals by calling
sigwait