CS252-Slides-2015-to..

advertisement
CS252: Systems Programming
Ninghui Li
Based on Slides by Prof. Gustavo Rodriguez-Rivera
Topic 13: Condition Variable, Read/Write Lock, and
Deadlock
Pseudo-Code Implementing
Semaphore Using Mutex Lock
sem_post(sem_t *sem){
sem_wait(sem_t *sem){
lock(sem->mutex);
lock(sem -> mutex);
sem -> count--;
sem ->count++;
if(sem->count < 0){
if(sem->count < 0){
unlock(sem->mutex);
wake up a thread;
wait();
}
} else {
unlock(sem->mutex);
unlock(sem->mutex)
}
}
Think about a context
}
switch here.
Assume that wait() causes a thread to be blocked.
What could go wrong? How to fix it?
Condition Variable
What we need is the ability to wait on a condition while
simultaneously giving up the mutex lock.
Condition Variable (CV):
A thread can wait on a CV; it will be blocked until
another thread call signal on the CV
A condition variable is always used in conjunction
with a mutex lock.
The thread calling wait should hold the lock, and the
wait call will releases the lock while going to wait
Using Condition Variable
Declaration:
#include <pthread.h>
pthread_cond_t cv;
Initialization:
pthread_cond_init(&cv, pthread_condattr_t *attr);
Wait on the condition variable:
int pthread_cond_wait(pthread_cond_t *cv,
pthread_mutex_t *mutex);
• The calling threshold should hold mutex; it will be
released atomically while start waiting on cv
• Upon successful return, the thread has re-aquired the
mutex; however, the thread waking up and reaquiring
the lock is not atomic.
Using Condition Variable
Waking up waiting threads:
int pthread_cond_signal(pthread_cond_t *cv);
• Unblocks one thread waiting on cv
int pthread_cond_broadcast(pthread_cond_t *cv);
• Unblocks all threads waiting on cv
•
The two methods can be called with or without holding
the mutex that the thread calls wait with; but it is better
to call it while holding the mutex
What is a Condition Variable?
Each Condition Variable has a queue of blocked threads
The cond_wait(cv, mutex) call adds the calling thread to
cv’s queue, while releasing mutex;
The call returns when the thread is unblocked (by
another thread calling cond_signal), and the thread
obtaining the mutex
The cond_signal(cv) call removes one thread from the
queue and unblocks it.
Implementing Semaphore
using Mutex and Cond Var
struct semaphore {
pthread_cond_t
cond;
pthread_mutex_t
mutex;
int
count;
};
typedef struct semaphore semaphore_t;
int semaphore_wait (semaphore_t *sem) {
int res = pthread_mutex_lock(&(sem->mutex));
if (res != 0) return res;
// error
sem->count --;
while (sem->count < 0) {
res= pthread_cond_wait(&(sem->cond),&(sem->mutex));
}
pthread_mutex_unlock(&(sem->mutex));
return res;
Implementing Semaphore
using Mutex and Cond Var
int semaphore_post (semaphore_t *sem) {
int res = pthread_mutex_lock(&(sem->mutex));
if (res != 0)
return res;
sem->count ++;
if (sem->count <= 0) {
res = pthread_cond_signal(&(sem->cond));
}
pthread_mutex_unlock(&(sem->mutex));
return res;
}
An Alternative and Buggy
Implementation
int semaphore_wait (semaphore_t *sem) {
pthread_mutex_lock(&(sem->mutex));
if (sem->count <= 0) {
pthread_cond_wait(&(sem->cond),&(sem->mutex));
}
sem->count --;
pthread_mutex_unlock(&(sem->mutex));
return res;
}
int semaphore_post (semaphore_t *sem) {
pthread_mutex_lock(&(sem->mutex));
sem->count ++;
pthread_cond_signal(&(sem->cond));
What bad thing could happen if the
pthread_mutex_unlock(&(sem->mutex)); two lines are switched?
}
Where is the Bug?
Assume sem->count == 1
T1
T2
T1 calls semaphore_wait()
if (sem->count <= 0) {
pthread_cond_wait(…);
}
sem->count --;
0
T1 continues
T2 calls semaphore_wait()
if (sem->count <= 0) {
pthread_cond_wait(…);
}
T2 waits
T1 calls semaphore_post()
sem->count ++;
1
pthread_cond_signal(…);
T3
Where is the Bug?
T1
Assume sem->count == 1
T2
T2 wakes up
T3
T3 calls semaphore_wait()
if (sem->count <= 0) {
pthread_cond_wait(…);
}
sem->count --;
0
T3 continues
T2 obtains mutex
sem->count --;
T2 continues
-1
Both T2 and T3 are able to proceed now. This will not happen if
while (sem->count <= 0) { pthread_cond_wait(…);
} is used
Using while versus if when
using cond_wait
int semaphore_wait (…) {
pthread_mutex_lock (…)
while (sem->count<=0) {
pthread_cond_wait
(&(sem->cond),
&(sem->mutex));
}
sem->count --;
thread_mutex_unlock (…)
}
int semaphore_wait (…) {
pthread_mutex_lock (…)
if (sem->count <=0) {
pthread_cond_wait
(&(sem->cond),
&(sem->mutex));
}
sem->count --;
thread_mutex_unlock (…)
}
The left version is correct and the right version is wrong.
• Because waking up and obtaining mutex is not atomic.
• The condition sem->count<=0 may no longer hold
when cond_wait returns control to the thread.
• Using while is also a defense against spurious wakeup
Usage of Semaphore: Bounded
Buffer
Implement a queue that has two functions


enqueue() - adds one item into the queue. It blocks if
queue if full
dequeue() - remove one item from the queue. It blocks
if queue is empty
Strategy:


Use an _emptySem semaphore that dequeue() will use
to wait until there are items in the queue
Use a _fullSem semaphore that enqueue() will use to
wait until there is space in the queue.
Bounded Buffer
#include <pthread.h>
#include <semaphore.h>
enum {MaxSize = 10};
class BoundedBuffer{
int _queue[MaxSize];
int _head;
int _tail;
mutex_t _mutex;
sem_t _emptySem;
sem_t _fullSem;
public:
BoundedBuffer();
void enqueue(int val);
int dequeue();
};
BoundedBuffer::BoundedBuffer() {
_head = 0;
_tail = 0;
pthtread_mutex_init(&_mutex,
NULL);
sem_init(&_emptySem, 0, 0);
sem_init(&_fullSem, 0, MaxSize);
}
Bounded Buffer
void
int
BoundedBuffer::enqueue(int val) BoundedBuffer::dequeue()
{
{
sem_wait(&_fullSem);
mutex_lock(_mutex);
_queue[_tail]=val;
_tail = (_tail+1)%MaxSize;
mutex_unlock(_mutex);
sem_post(_emptySem);
sem_wait(&_emptySem);
mutex_lock(_mutex);
int val = _queue[_head];
_head = (_head+1)%MaxSize;
mutex_unlock(_mutex);
sem_post(_fullSem);
return val;
}
}
Bounded Buffer
Assume queue is empty
T1
T2
T3
v=dequeue()
sem_wait(&_emptySem);
_emptySem.count==-1
wait
v=dequeue()
sem_wait(&_emptySem);
_emptySem.count==-2
wait
enqueue(6)
sem_wait(&_fullSem)
put item in queue
sem_post(&emptySem)
_emptySem.count==-1
wakeup T1
T1 continues
Get item from queue
Bounded Buffer
Assume queue is empty
T1
T2
……
T10
enqueue(1)
sem_wait(&_fullSem);
_fullSem.count==9
put item in queue
enqueue(2)
sem_wait(&_fullSem);
_fullSem.count==8
put item in queue
enqueue(10)
sem_wait(&_fullSem);
_fullSem.count==0
put item in queue
Bounded Buffer
T11
T12
enqueue(11)
sem_wait(&_fullSem);
_fullSem.count==-1
wait
val=dequeue()
sem_wait(&_emptySem);
_emptySem.count==9
get item from queue
sem_post(&_fullSem)
_fullSem.count==0
wakeup T11
Bounded Buffer Notes
The counter for _emptySem represents the
number of items in the queue
The counter for _fullSem represents the
number of spaces in the queue.
Mutex locks are necessary to ensure that
queue access is atomic.
Clicker Question 1
A POSIX pthread mutex may be normal/fast, recursive, or
error-check based on their behavior on (1) a thread that
holds the mutex calls lock again; (2) a thread that does
not hold the mutex calls unlock.
Which of the following describes a normal/fast mutex
A. (1) calling thread can continue; (2) report error
B. (1) report error; (2) succeeds
C. (1) calling thread is blocked; (2) undefined by POSIX
D. (1) report error; (2) report error
E. None of the above
Clicker Question 2
A binary semaphore can sometimes be used in place of
a mutex; what is its behavior in the following
situations: (1) a thread that has called sem_wait calls
sem_wait again; (2) a thread that has not called
sem_wait calls sem_post.
A. (1) calling thread continue; (2) report error
B. (1) calling thread blocked; (2) succeeds
C. (1) calling thread continue; (2) succeeds
D. (1) calling thread blocked; (2) report error
E. None of the above
Clicker Question 3
Consider the following code
int semaphore_post
(semaphore_t *sem)
{
pthread_mutex_lock
(&(sem->mutex));
sem->count ++;
pthread_mutex_unlock
(&(sem->mutex));
pthread_cond_signal
(&(sem->cond));
}
What may go wrong?
A. Too many threads
may be able to
continue
B. A thread already
waiting may not be
correctly woke up
C. A new thread may
not be corrected
woke up
D. All of the above
E. None of the above
Read/Write Locks
They are locks for data structures that can
be read by multiple threads simultaneously (
multiple readers ) but that can be modified
by only one thread at a time.
Example uses: Data Bases, lookup tables,
dictionaries etc where lookups are more
frequent than modifications.
Read/Write Locks
Multiple readers may read the data structure
simultaneously
Only one writer may modify it and it needs to
exclude the readers.
Interface:
ReadLock() – Lock for reading. Wait if there are
writers holding the lock
ReadUnlock() – Unlock for reading
WriteLock() - Lock for writing. Wait if there are
readers or writers holding the lock
WriteUnlock() – Unlock for writing
Read/Write Locks
Threads:
R1
R2
--- --RL
RL
R3
---
R4
---
W1
---
RL
WL
wait
RU
RU
RU
continue
RL
Wait
WU
continue
rl
ru
wl
wu
=
=
=
=
readLock;
readUnlock;
writeLock;
writeUnlock;
Read/Write Locks
Implementation
class RWLock
{
int _nreaders;
//Controls access
//to readers/writers
sem_t _semAccess;
mutex_t _mutex;
public:
RWLock();
void readLock();
void writeLock();
void readUnlock();
void writeUnlock();
};
RWLock::RWLock()
{
_nreaders = 0;
sem_init( &semAccess, 1 );
mutex_init( &_mutex );
}
Read/Write Locks
Implementation
void RWLock::readLock()
{
mutex_Lock( &_mutex );
_nreaders++;
if( _nreaders == 1 )
{
//This is the
// first reader
//Get sem_Access
sem_wait(&_semAccess);
}
mutex_unlock( &_mutex );
}
void RWLock::readUnlock()
{
mutex_lock( &_mutex );
_nreaders--;
if( _nreaders == 0 )
{
//This is the last reader
//Allow one writer to
//proceed if any
sem_post( &_semAccess );
}
mutex_unlock( &_mutex );
}
Read/Write Locks
Implementation
void RWLock::writeLock()
{
sem_wait( &_semAccess );
}
void RWLock::writeUnlock()
{
sem_post( &_semAccess );
}
Read/Write Locks Example
Threads:
R1
R2
R3
W1
W2
----------- ------------ --------------- -------readLock
nreaders++(1)
if (nreaders==1)
sem_wait
continue
readLock
nreaders++(2)
readLock
nreaders++(3)
writeLock
sem_wait
(block)
Read/Write Locks Example
Threads:
R1
-----------
R2
R3
------------ --------
W1
W2
-------- -------writeLock
sem_wait
(block)
readUnlock()
nreaders—(2)
readUnlock()
nreaders—(1)
readUnlock()
nreaders—(0)
if (nreaders==0)
sem_post
W1 continues
writeUnlock
sem_post
W2 continues
Read/Write Locks Example
Threads: (W2 is holding lock in write mode)
R1
R2
R3
W1
W2
----------- ------------ --------------- -------readLock
mutex_lock
nreaders++(1)
if (nreaders==1)
sema_wait
block
readLock
mutex_lock
block
writeUnlock
sema_post
R1 continues
mutex_unlock
R2 continues
Notes on Read/Write Locks
 Fairness in locking: First-come-first serve
 Mutexes and semaphores are fair. The thread that has
been waiting the longest is the first one to wake up.
 This implementation of read/write locks suffers from
“starvation” of writers. That is, a writer may never be
able to write if the number of readers is always greater
than 0.
Write Lock Starvation
(Overlapping readers)
Threads:
R1
R2
--- --RL
RL
R3
---
R4
---
W1
---
RL
WL
wait
RU
RL
RU
RU
RL
RU
RL
rl
ru
wl
wu
=
=
=
=
readLock;
readUnlock;
writeLock;
writeUnlock;
Review Questions
What are Condition Variables? What is the
behavior of wait/signal on CV?
How to implement semaphores using using CV
and Mutex?
How to implement bounded buffer using
semaphores?
Review Questions
What are read/write locks? What is the behavior
of read/write lock/unlock?
How to implement R/W locks using semaphore?
Why the implementation given in the slides can
cause writer starvation?
How to Implement a read/write lock where writer
is preferred (i.e., when a writer is waiting, no
reader can gain read lock and must wait until all
writers are finished)?
Download