Sunteți pe pagina 1din 27

Lecture 10

Chapter 13-14 of Robbins Book

Thread Syncronization

BIL 244 – System Programming


Overview

• To 'protect' an area of code that is a 'critical section'


basically 3 different methods are used.
– mutex,
– semaphore,
– monitor

• The implementation of each is very different but


mostly
– mutex can be implemented in hardware
– semaphores used by low level languages
– monitors used by high level languages

BIL 244 – System Programming


Overview

• At its most basic a semaphore is a struct consisting


of:
– data (an integer indicating the semaphore state)
– operation (either of wait/signal)
• If the data is only allowed to take values of 0 or 1, this
is a binary semaphore – i.e. a mutex
• If the data is allowed to take any non-zero value, this is
known as a general semaphore
• If the semaphore has a value of zero, any process that
tries to decrement it (i.e. obtain the lock) will be
blocked until the semaphore has a non-zero value
BIL 244 – System Programming
POSIX Syncronization

• The POSIX thread library contains several


synchronization constructs.

• The simplest of these is the mutex lock.

• A mutex, or mutex lock, is a special variable that can be


either in the locked state or the unlocked state.

BIL 244 – System Programming


Mutex Locks

• If the mutex is locked, it has a distinguished thread that


holds or owns the mutex.
• If no thread holds the mutex, we say the mutex is
unlocked, free or available.
• The mutex also has a queue for the threads that are
waiting to hold the mutex.
• The order in which the threads in the mutex queue obtain
the mutex is determined by the thread-scheduling policy
• POSIX does not require that any particular policy be
implemented (does not require that this queue be
accessed FIFO).
BIL 244 – System Programming
Mutex Locks

• A mutex is meant to be held for only a short period of


time.
• It is usually used to protect access to a shared variable.
idea:
lock the mutex
critical section
unlock the mutex
• Only the owner of the mutex should unlock the mutex.
• Do not lock a mutex that is already locked.
• Do not unlock a mutex that is not already locked.

BIL 244 – System Programming


Creating and initializing a mutex

• We will only cover the simplest (and most often used)


method of using POSIX mutexes
• Since a mutex is meant to be used by multiple threads, it
is usually declared to have static storage class.
• It can be defined inside a function using the static
qualifier if it will only be used by that function or it can
be defined at the top level.
• A mutex must be initialized before it is used.
• This can be done when the mutex is defined, as in:
pthread_mutex_t mymutex =PTHREAD_MUTEX_INITIALIZER;

BIL 244 – System Programming


Creating and initializing a mutex

• The mutex parameter of pthread_mutex_init is a pointer


to the mutex to be initialized. Pass NULL for the attr
parameter of pthread_mutex_init to initialize a mutex
with the default attributes. Otherwise, first create and
initialize a mutex attribute object in a manner similar to
that used for thread attribute objects.
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,


const pthread_mutexattr_t *restrict attr);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

BIL 244 – System Programming


Destroying a mutex

• The pthread_mutex_destroy function destroys the


mutex referenced by its parameter. The mutex parameter is
a pointer to the mutex to be destroyed.
• A pthread_mutex_t variable that has been destroyed
with pthread_mutex_destroy can be reinitialized with
pthread_mutex_init.

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

BIL 244 – System Programming


Locking and unlocking a mutex

• POSIX has two functions, pthread_mutex_lock and


pthread_mutex_trylock for acquiring a mutex.
• The pthread_mutex_lock function blocks until the mutex is
available, while the pthread_mutex_trylock always returns
immediately.
• The pthread_mutex_unlock function releases the specified mutex.
• All three functions take a single parameter, mutex, a pointer to a
mutex.

#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);

BIL 244 – System Programming


The following code segment uses a mutex to protect a critical section

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;


pthread_mutex_lock(&mylock);
/* critical section */
pthread_mutex_unlock(&mylock);
A counter that can be accessed by multiple threads

#include <pthread.h>
static int count = 0;
static pthread_mutex_t countlock = PTHREAD_MUTEX_INITIALIZER;

int increment(void) { /* increment the counter */


int error;
if (error = pthread_mutex_lock(&countlock))
return error;
count++;
return pthread_mutex_unlock(&countlock);
}

int decrement(void) { /* decrement the counter */


int error;
if (error = pthread_mutex_lock(&countlock))
return error;
count--;
return pthread_mutex_unlock(&countlock);
}

int getcount(int *countp) { /* retrieve the counter */


int error;
if (error = pthread_mutex_lock(&countlock))
return error;
*countp = count;
return pthread_mutex_unlock(&countlock);
}
At-Most-Once and At-Least-Once-
Execution
• If a mutex isn't statically initialized, the program must call
pthread_mutex_init before using any of the other mutex
functions.
• Care must be taken to call pthread_mutex_init before any thread
accesses a mutex, but having each thread initialize the mutex doesn't
work either. The effect of calling pthread_mutex_init for a mutex
that has already been initialized is not defined.
• The notion of single initialization is so important that POSIX
provides the pthread_once function to ensure these semantics.
• The once_control parameter must be statically initialized with
PTHREAD_ONCE_INIT.
• The init_routine is called the first time pthread_once is called with
a given once_control, and init_routine is not called on
subsequent calls. When a thread returns from pthread_once
without error, the init_routine has been completed by some
thread.
BIL 244 – System Programming
At-Most-Once and At-Least-Once-
Execution

#include <pthread.h>
int pthread_once(pthread_once_t *once_control,
void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;

• If successful, pthread_once returns 0. If


unsuccessful, pthread_once returns a nonzero error
code.
• No mandatory errors are defined for pthread_once .

BIL 244 – System Programming


Condition Variables

• This part is left to the Students

BIL 244 – System Programming


Signal Handling and Threads

• All threads in a process share the process signal handlers, but each
thread has its own signal mask.
• The interaction of threads with signals involves several
complications because threads can operate asynchronously with
signals
• Signals such as SIGFPE (floating-point exception) are synchronous to
the thread that caused them (i.e., they are always generated at the
same point in the thread's execution)
• Other signals are asynchronous because they are not generated at a
predictable time nor are they associated with a particular thread
• If several threads have an asynchronous signal unblocked, the
thread runtime system selects one of them to handle the signal.
• Signals can also be directed to a particular thread with
pthread_kill.

BIL 244 – System Programming


Directing a signal to a particular
thread
• The pthread_kill function requests that signal number sig be
generated and delivered to the thread specified by thread.
#include <signal.h>
#include <pthread.h>

int pthread_kill(pthread_t thread, int sig);


• If successful, pthread_kill returns 0. If unsuccessful,
pthread_kill returns a nonzero error code. In the latter case, no
signal is sent
• Although pthread_kill delivers the signal to a particular thread,
the action of handling it may affect the entire process. A common
confusion is to assume that pthread_kill always causes process
termination (but this is not the case)
• The pthread_kill just causes a signal to be generated for the
thread
BIL 244 – System Programming
Masking signals for threads

• While signal handlers are process-wide, each thread has


its own signal mask.
• A thread can examine or set its signal mask with the
pthread_sigmask function, which is a generalization
of sigprocmask to threaded programs
• The sigprocmask function should not be used when
the process has multiple threads, but it can be called by
the main thread before additional threads are created

BIL 244 – System Programming


Masking signals for threads

• The how and set parameters specify the way the signal mask is
to be modified.
• If the oset parameter is not NULL, the pthread_sigmask
function sets *oset to the thread's previous signal mask.
#include <signal.h>
#include <pthread.h>

int pthread_sigmask(int how, const sigset_t *restrict set,


sigset_t *restrict oset);

• If successful, pthread_sigmask returns 0. If unsuccessful,


pthread_sigmask returns a nonzero error code.
• The pthread_sigmask function returns EINVAL if how is not
valid.

BIL 244 – System Programming


Dedicating threads for signal
handling
• A recommended strategy for dealing with signals in
multithreaded processes is to dedicate particular threads to
signal handling
• The main thread blocks all signals before creating the
threads.
• The signal mask is inherited from the creating thread, so all
threads have the signal blocked.
• The thread dedicated to handling the signal then executes
sigwait on that signal
• Alternatively, the thread can use pthread_sigmask to
unblock the signal. The advantage of using sigwait is that
the thread is not restricted to async-signal-safe functions.
BIL 244 – System Programming
Readers and Writers

• The reader-writer problem refers to a situation in which


a resource allows two types of access (reading and
writing).
• One type of access must be granted exclusively (e.g.,
writing), but the other type may be shared (e.g.,
reading).
• For example, any number of processes can read from
the same file without difficulty, but only one process
should modify the file at a time.

BIL 244 – System Programming


Readers and Writers

• Two common strategies for handling reader-writer


synchronization are called strong reader synchronization
and strong writer synchronization
• Strong reader synchronization always gives preference to
readers, granting access to readers as long as a writer is not
currently writing
• Strong writer synchronization always gives preference to
writers, delaying readers until all waiting or active writers
complete
• An airline reservation system would use strong writer
preference, since readers need the most up-to-date
information. On the other hand, a library reference
database might want to give readers preference.

BIL 244 – System Programming


Readers and Writers

• POSIX provides read-write locks that allow multiple


readers to acquire a lock, provided that a writer does not
hold the lock.
• POSIX states that it is up to the implementation whether
to allow a reader to acquire a lock if writers are blocked
on the lock.
• POSIX read-write locks are represented by variables of
type pthread_rwlock_t.
• Programs must initialize pthread_rwlock_t variables
before using them for synchronization by calling
pthread_rwlock_init.
BIL 244 – System Programming
Readers and Writers

• The rwlock parameter is a pointer to a read-write lock.


• Pass NULL for the attr parameter of pthread_rwlock_init
to initialize a read-write lock with the default attributes.
Otherwise, first create and initialize a read-write lock
attribute object in a manner similar to that used for thread
attribute objects.
#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,


const pthread_rwlockattr_t *restrict attr);

BIL 244 – System Programming


Readers and Writers

• The pthread_rwlock_destroy function destroys the


read-write lock referenced by its parameter
• The rwlock parameter is a pointer to a read-write lock. A
pthread_rwlock_t variable that has been destroyed with
pthread_rwlock_destroy can be reinitialized with
pthread_rwlock_init.

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)

• If successful, pthread_rwlock_destroy returns 0. If


unsuccessful, it returns a nonzero error code
BIL 244 – System Programming
Readers and Writers

• The pthread_rwlock_rdlock and pthread_rwlock_tryrdlock


functions allow a thread to acquire a read-write lock for reading.
• The pthread_rwlock_wrlock and pthread_rwlock_trywrlock
functions allow a thread to acquire a read-write lock for writing.
• The pthread_rwlock_rdlock and pthread_rwlock_wrlock
functions block until the lock is available, whereas
pthread_rwlock_tryrdlock and pthread_rwlock_trywrlock
return immediately.
• The pthread_rwlock_unlock function causes the lock to be
released.
• These functions require that a pointer to the lock be passed as a
parameter.

BIL 244 – System Programming


Readers and Writers

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);


int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

• If successful, these functions return 0. If unsuccessful, these


functions return a nonzero error code.
• The pthread_rwlock_tryrdlock and
pthread_rwlock_trywrlock functions return EBUSY if the
lock could not be acquired because it was already held.

BIL 244 – System Programming

S-ar putea să vă placă și