Lecture 14
Lecture 14
Condition Variables
Optional reading:
Operating Systems: Principles and Practice (2nd Edition): Sections 5.2-5.4
and Section 6.5
This document is copyright (C) Stanford Computer Science and Nick Troccoli, licensed under
Creative Commons Attribution 2.5 License. All rights reserved.
Based on slides and notes created by John Ousterhout, Jerry Cain, Chris Gregg, and others.
NOTICE RE UPLOADING TO WEBSITES: This content is protected and may not be shared, 1
uploaded, or distributed. (without expressed written permission)
CS111 Topic 3: Multithreading, Part 1
Topic 3: Multithreading - How can we have concurrency within a single
process? How does the operating system support this?
Race
Multithreading Condition Multithreading
conditions and
Introduction Variables Patterns
locks
Lecture 12 Last Lecture This Lecture Lecture 15
2
Learning Goals
• Learn about ways to add constraints to our programs to prevent deadlock
• Learn how condition variables can let threads signal to each other and wait for
conditions to become true
3
Plan For Today
• Recap: mutexes and dining philosophers
• Encoding resource constraints
• Condition Variables
cp -r /afs/ir/class/cs111/lecture-code/lect14 . 4
Plan For Today
• Recap: mutexes and dining philosophers
• Encoding resource constraints
• Condition Variables
cp -r /afs/ir/class/cs111/lecture-code/lect14 . 5
Mutexes
A mutex (”mutual exclusion”) is a variable type that lets us enforce the pattern
of only 1 thread having access to something at a time.
• You make a mutex for each distinct thing you need to limit access to.
• You call lock() on the mutex to attempt to take the lock
• You call unlock() on the mutex when you are done to give the lock back
• A way to add a constraint to your program: “only one thread may access or
execute this at a time”.
6
Ticket Agents
static void sellTickets(size_t id, size_t& remainingTickets, mutex&
counterLock) {
while (true) {
counterLock.lock(); // only 1 thread can proceed at a time
if (remainingTickets == 0) {
counterLock.unlock(); // must give up lock before exiting
break;
}
size_t myTicket = remainingTickets;
remainingTickets--;
counterLock.unlock(); // once thread passes here, another can go
sleep_for(500); // simulate "selling a ticket"
...
7
Deadlock
Deadlock occurs when multiple threads are all blocked, waiting on a resource
owned by one of the other threads. None can make progress! Example:
Thread A Thread B
mutex1.lock(); mutex2.lock();
mutex2.lock(); mutex1.lock();
... ...
E.g. if thread A executes 1 line, then thread B executes 1 line, deadlock!
One prevention technique - prevent circularities: all threads request resources in
the same order (e.g., always lock mutex1 before mutex2.)
Another – limit number of threads competing for a shared resource
8
Deadlock Example: Dining
Philosophers Simulation
• Five philosophers sit around a circular table, eating spaghetti
• There is one fork for each of them
• Each philosopher thinks, then eats, and repeats this three times for their
three daily meals.
• To eat, a philosopher must grab the fork on their left and the fork on their
right. Then they chow on spaghetti to nourish their big, philosophizing brain.
• When they're full, they put down the forks in the same order they picked them
up and return to thinking for a while.
• To think, a philosopher keeps to themselves for some amount of
time. Sometimes they think for a long time, and sometimes they barely think
at all.
9
Dining Philosophers
eat is modeled as grabbing the two forks, sleeping for some amount of time,
and putting the forks down.
dining-philosophers-with-deadlock.cc 11
Plan For Today
• Recap: mutexes and dining philosophers
• Encoding resource constraints
• Condition Variables
cp -r /afs/ir/class/cs111/lecture-code/lect14 . 12
Encoding Resource Constraints
Goal: we must encode resource constraints into our program.
Example: how many philosophers can try to eat at the same time? Four.
• Alternatively: how many philosophers can eat at the same time? Two.
• Why might the first one be better? Imposes less bottlenecking while still
solving the issue.
13
Tickets, Please…
int main(int argc, const char *argv[]) {
mutex forks[kNumForks];
thread philosophers[kNumPhilosophers];
for (size_t i = 0; i < kNumPhilosophers; i++) {
philosophers[i] = thread(philosopher, i, ref(forks[i]),
ref(forks[(i + 1) % kNumPhilosophers]),
ref(permits), ref(permitsLock));
}
for (thread& p: philosophers) p.join();
return 0;
} 14
Tickets, Please…
A philosopher thinks and eats, and repeats this 3 times.
15
Tickets, Please…
static void eat(size_t id, mutex& left, mutex& right,
size_t& permits, mutex& permitsLock) {
waitForPermission(permits, permitsLock);
left.lock();
right.lock();
cout << oslock << id << " starts eating om nom nom
nom." << endl << osunlock;
sleep_for(getEatTime());
cout << oslock << id << " all done eating." << endl
<< osunlock;
grantPermission(permits, permitsLock);
left.unlock();
right.unlock();
16
grantPermission
To put a permit back, increment the counter by 1 and continue.
17
waitForPermission
• If there are permits, decrement the counter by 1 and continue
• If there aren’t permits, wait for a permit, then decrement by 1 and continue
cp -r /afs/ir/class/cs111/lecture-code/lect14 . 21
Condition Variables
A condition variable is a variable type that can be shared across threads and
used for one thread to notify other thread(s) when something happens.
Conversely, a thread can also use this to wait until it is notified by another
thread.
• You make one for each distinct event you need to wait / notify for.
• We can call wait on the condition variable to sleep until another thread signals
this condition variable (no busy waiting).
• You call notify_all on the condition variable to send a notification to all waiting
threads and wake them up.
• Analogy: radio station – broadcast and tune in
22
Condition Variables
1. Identify a single kind of event that we need to wait / notify for
2. Ensure there is proper state to check if the event has happened
3. Create a condition variable and share it among all threads either waiting for
that event to happen or triggering that event
4. Identify who will notify that this happens, and have them notify via the
condition variable
5. Identify who will wait for this to happen, and have them wait via the
condition variable
23
Condition Variables
1. Identify a single kind of event that we need to wait / notify for
2. Ensure there is proper state to check if the event has happened
3. Create a condition variable and share it among all threads either waiting for
that event to happen or triggering that event
4. Identify who will notify that this happens, and have them notify via the
condition variable
5. Identify who will wait for this to happen, and have them wait via the
condition variable
26
Condition Variables
int main(int argc, const char *argv[]) {
mutex forks[kNumForks];
size_t permits = kNumForks - 1;
mutex permitsLock;
condition_variable_any permitsCV;
thread philosophers[kNumPhilosophers];
for (size_t i = 0; i < kNumPhilosophers; i++) {
philosophers[i] = thread(philosopher, i, ref(forks[i]),
ref(forks[(i + 1) % kNumPhilosophers]),
ref(permits), ref(permitsCV),
ref(permitsLock));
}
for (thread& p: philosophers) p.join();
return 0;
27
}
Condition Variables
1. Identify a single kind of event that we need to wait / notify for
2. Ensure there is proper state to check if the event has happened
3. Create a condition variable and share it among all threads either waiting for
that event to happen or triggering that event
4. Identify who will notify that this happens, and have them notify via the
condition variable
5. Identify who will wait for this to happen, and have them wait via the
condition variable
32
waitForPermission (In Progress)
If no permits are available, we must wait until one becomes available.
Key Idea: we must give up ownership of the lock when we wait, so that
someone else can put a permit back.
This is the final implementation with the final version of wait() that takes a
mutex parameter and which is called in a while loop. Let’s build our way to this
solution!
34
Deadlock, Round 2
static void waitForPermission(size_t& permits, condition_variable_any&
permitsCV, mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
What race condition here could lead to deadlock? Hints:
• As soon as we release a lock, another thread can use it
• if a thread isn’t waiting on a CV, it won’t get a notification from another thread
Respond on PollEv: pollev.com/cs111
or text CS111 to 22333 once to join. 35
36
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
permits = 0
PERMIT
Thread #1 Thread #2 37
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
z permits = 0
z
z
PERMIT
Thread #1 Thread #2 38
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
Thread #1 Thread #2 39
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
Thread #1 Thread #2 40
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
Thread #1 Thread #2 41
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
Thread #1 Thread #2 42
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
Thread #1 Thread #2 43
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
Thread #1 Thread #2 44
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
z permits = 1
z
z
Thread #1 Thread #2 45
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
permits--;
permitsLock.unlock();
}
z permits = 1
z
z *100 years later*
Thread #1 Thread #2 46
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsLock.unlock();
// AIR GAP HERE – someone could acquire the lock before we wait
permitsCV.wait(); // (note: not final form of wait)
permitsLock.lock();
}
...
Key ideas:
• We must release the lock when waiting so someone else can put a permit back
(which requires having the lock)
• But if we release the lock before calling wait, someone else could swoop in and
put a permit back before we call wait(), meaning we will miss the notification!
If that is the last notification, we may wait forever. 47
Deadlock: waitForPermission
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
Spoiler: there is a race condition here that could lead to negative permits if
multiple threads are waiting on a permit (e.g. say we limit permits to 3) and just
1 is returned.
50
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 0
PERMIT
Thread #1 51
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 0
z We need to wait
z
z for a permit in
order to eat.
PERMIT
Thread #1 52
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 0
All done eating! I
will return my permit. z z
z z
z
z
PERMIT
Thread #1 53
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 1
All done eating! I
will return my permit. z z
z z
z
z
Thread #1 54
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
Thread #1 55
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
Thread #1 56
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 1
z z
z
z z
z
Thread #1 57
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 1
z z
z
z z
z
Thread #1 58
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 0
z z
z
z z
z
PERMIT
Thread #1 59
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 0
z z
z
z z
z
PERMIT
Thread #1 60
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = 0
z z
z
z z
z
PERMIT
Thread #1 61
Thread #2 Thread #3
waitForPermission Over-permitting
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
permits = <very large number>
z z
z
z z
z
FAKE
PERMIT PERMIT
??
Thread #1 62
Thread #2 Thread #3
waitForPermission Over-permitting
Key Idea: If we are waiting and then woken up by a notification, it’s possible by
the time we exit wait(), there are no permits, so we must wait again.
• Note: wait() reacquires the lock before returning
Solution: we must call wait() in a loop, in case we must call it again to wait
longer.
dining-philosophers-with-cv-wait.cc 64
Spurious Wakeups
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
while (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}
It turns out that in addition to this reason, condition variables can have spurious
wakeups – they wake us up even when not being notified by another thread!
Thus, we should always wrap calls to wait in a while loop.
65
Condition Variable Key Takeaways
A condition variable is a variable type that can be shared across threads and
used for one thread to notify other thread(s) when something
happens. Conversely, a thread can also use this to wait until it is notified by
another thread.
• We can call wait(lock) to sleep (no busy waiting) until another thread signals
this condition variable. The condition variable will unlock and re-lock the
specified lock for us.
• This is necessary because we must give up the lock while waiting so another thread may
return a permit, but if we unlock before waiting, there is a race condition.
• We can call notify_all() to send a signal to waiting threads and wake them up.
• We call wait(lock) in a loop in case we are woken up but must wait longer
• This could happen if multiple threads are woken up for a single new permit, or because
of spurious wakeups. 66
Condition Variables
1. Identify a single kind of event that we need to wait / notify for
2. Ensure there is proper state to check if the event has happened
3. Create a condition variable and share it among all threads either waiting for
that event to happen or triggering that event
4. Identify who will notify that this happens, and have them notify via the
condition variable
5. Identify who will wait for this to happen, and have them wait via the
condition variable
67
Recap
• Recap: mutexes and dining philosophers Lecture 14 takeaway:
• Encoding resource constraints Condition variables let us wait
• Condition Variables on an event to occur and
notify other threads that an
event has occurred, all
without busy waiting.
cp -r /afs/ir/class/cs111/lecture-code/lect14 . 68