Académique Documents
Professionnel Documents
Culture Documents
Stefano Zacchiroli
zack@pps.univ-paris-diderot.fr
2013–2014
URL http://upsilon.cc/zack/teaching/1314/progsyst/
Copyright © 2012–2013 Stefano Zacchiroli
License Creative Commons Attribution-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-sa/4.0/deed.en_US
2 Mutex
3 Condition variables
4 Thread safety
5 Going further
2 Mutex
3 Condition variables
4 Thread safety
5 Going further
By default, each process has only one thread of control (or “thread”),
i.e. only one set of instructions being executed at any given time.
Intuition
new process = new execution point + new address space
new thread = new execution point
0x00000000
FAIL.
FAIL. or not. . .
Gory details
in threaded programs ernno is actually a macro
. . . that expands to a function call
. . . which returns a modifiable lvalue
Bottom line
errno has been adapted to cope well with threads: each thread has
its own errno. Don’t worry about it.
Usual convention
success: return 0 (or some positive informative value)
failure: return -1; set errno
Why? Because.
(and also because every errno access in threaded programs costs you a
function call overhead)
Sample usage:
pthread_t thread ;
int s ;
s = pthread_func(& thread , . . . ) ;
i f ( s != 0)
err_sys_p ( s , " pthread_func " ) ;
Stefano Zacchiroli (Paris Diderot) POSIX Threads 2013–2014 16 / 68
Compiling with Pthreads (on Linux)
–pthread
Adds support for multithreading with the pthreads
library. This option sets flags for both the preprocessor and
linker.
— manpage CC(1)
Why?
Intuition: you need to tell the compiler toolchain to do all sort of
thread-specific treatments, like the errno trick.
The main thread exists by default. We can add extra threads with:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start)(void *), void *arg);
Returns: 0 on success, positive errno on error
The main thread exists by default. We can add extra threads with:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start)(void *), void *arg);
Returns: 0 on success, positive errno on error
#include <pthread.h>
void pthread_exit(void *retval);
Returns: does not return
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Returns: 0 on success, positive errno on error
Demo
i f ( argc != 2)
e r r _ q u i t ( " Usage : hellos_pthread THREAD_NO" ) ;
threads_no = a t o l ( argv [ 1 ] ) ; // TODO: error checking
free ( ts ) ;
e x i t ( EXIT_SUCCESS ) ;
} / * end of hellos_pthread . c * /
Demo
#include <pthread.h>
int pthread_detach(pthread_t thread);
Returns: 0 on success, positive errno on error
#include <pthread.h>
pthread_t pthread_self(void);
Returns: thread ID of the calling thread
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
Returns: nonzero value if t1 and t2 are equal, 0 otherwise
2 Mutex
3 Condition variables
4 Thread safety
5 Going further
Reminder:
Definition (critical section)
A critical section is a code snippet that accesses a shared resource
and that shall be executed atomically with respect to other (kernel
scheduling) “entities” (KSE) accessing the same shared resource.
Up to now:
entities = processes
shared resources = those sharable between processes
ñ specific memory pages, file descriptors, SysV structures, etc.
Now we add:
entities = threads
shared resources = all non-local variables by default (!)
loc = glob ;
loc ++;
glob = loc ;
}
loc = glob ;
loc ++;
glob = loc ;
}
loc = glob ;
loc ++;
glob = loc ;
}
s t a t i c long glob = 0;
return NULL ;
}
s = pthread_join ( t1 , NULL ) ;
i f ( s != 0) err_sys_p ( s , " pthread_join ( t1 ) " ) ;
s = pthread_join ( t2 , NULL ) ;
i f ( s != 0) err_sys_p ( s , " pthread_join ( t2 ) " ) ;
Demo
2000
l oc = gl ob;
Repeatedly:
l oc = gl ob;
l oc++;
gl ob = l oc;
3000
l oc++;
2001 gl ob = l oc; Key
Executing Waiting
on CPU for CPU
l oc++;
2001 gl ob = l oc; Key
Executing Waiting
on CPU for CPU
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
Returns: 0 on success, positive errno on error
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
Returns: 0 on success, positive errno on error
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
Returns: 0 on success, positive errno on error
s t a t i c long glob = 0;
s t a t i c pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER ;
s = pthread_join ( t1 , NULL ) ;
i f ( s != 0) err_sys_p ( s , " pthread_join ( t1 ) " ) ;
s = pthread_join ( t2 , NULL ) ;
i f ( s != 0) err_sys_p ( s , " pthread_join ( t2 ) " ) ;
Demo
2
on Linux x86-64, Intel i5, quad-core, 2.5 GHz
Stefano Zacchiroli (Paris Diderot) POSIX Threads 2013–2014 38 / 68
Outline
2 Mutex
3 Condition variables
4 Thread safety
5 Going further
for ( ; ; ) {
s = pthread_mutex_lock (&mtx ) ;
i f ( s != 0) err_sys_p ( s , " pthread_mutex_lock " ) ;
while ( goods > 0) {
goods−−;
p r i n t f ( " consuming . . . good ! \ n " ) ;
}
s = pthread_mutex_unlock (&mtx ) ;
i f ( s != 0) err_sys_p ( s , " pthread_mutex_unlock " ) ;
}
e x i t ( EXIT_SUCCESS ) ;
} / * end of pc−naive . c * /
Stefano Zacchiroli (Paris Diderot) POSIX Threads 2013–2014 43 / 68
P-C with mutex — example
Demo
Demo
Demo
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
Returns: 0 on success, positive errno on error
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
Returns: 0 on success, positive errno on error
3
*_wait does the following steps, in order:
1 unlock mutex
2 suspend execution of the calling thread until cond is signaled
3 lock mutex again
3
we will see in a bit why
Stefano Zacchiroli (Paris Diderot) POSIX Threads 2013–2014 47 / 68
Acting on condition variables (cont.)
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
Returns: 0 on success, positive errno on error
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
Returns: 0 on success, positive errno on error
Idea:
a single condition variable avail, meaning “goods are available”
if no goods are available, the consumer will wait on avail
each time the producer produces something, it signals avail to
awake the consumer (if he is asleep)
Demo
no polling, by design
consumer awaken only when needed
CPU back under control
pthread_mutex_unlock (&mtx ) ;
pthread_mutex_unlock (&mtx ) ;
What if:
other threads executing the same code are woken up together
with me, but execute first?
spurious wakeups happens? (i.e. waking up even if nobody has
signaled)
You’ll end up executing as if the condition were satisfied, while
that’s not necessarily the case. FAIL.
Stefano Zacchiroli (Paris Diderot) POSIX Threads 2013–2014 53 / 68
Wait subtleties (2) (cont.)
Bottom line: you should always recheck the predicate associated to a
condition variable immediately after wakeup
if it’s not satisfied, wait again
pthread_mutex_unlock (&mtx ) ;
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
Returns: 0 on success, positive errno on error
2 Mutex
3 Condition variables
4 Thread safety
5 Going further
s t a t i c i n t glob = 0;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
s t a t i c i n t glob = 0;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
2 Mutex
3 Condition variables
4 Thread safety
5 Going further
To reiterate:
[ Pthreads is a ] very large and complex API! we will only
scratch the surface. . .
more info
pthread_cancel(3), pthreads(7)
Don’t do that.
General idea:
signals can target individual threads or the process as a whole
signal dispositions are global, but signal masks are per-thread
good model: delegate signal management to a single thread,
protect all other threads with masks
bonus point: as the signal management thread has nothing else
to do, make it handle signal synchronously with:
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
Returns: 0 on success, positive errno on error