0% found this document useful (0 votes)
3 views

Lecture 14

This document covers lecture 14 of CS111, focusing on condition variables and multithreading concepts. It discusses the importance of mutexes to prevent race conditions, the concept of deadlock, and how to use condition variables for thread synchronization. The lecture also includes practical examples, such as the dining philosophers problem, to illustrate these concepts in programming.

Uploaded by

mippiyaya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

Lecture 14

This document covers lecture 14 of CS111, focusing on condition variables and multithreading concepts. It discusses the importance of mutexes to prevent race conditions, the concept of deadlock, and how to use condition variables for thread synchronization. The lecture also includes practical examples, such as the dining philosophers problem, to illustrate these concepts in programming.

Uploaded by

mippiyaya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 68

CS111, 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

assign4: implement several multithreaded programs while eliminating race conditions!

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.

static void eat(size_t id, mutex& left, mutex& right) {


left.lock();
right.lock();
cout << oslock << id << " starts eating om nom nom
nom." << endl << osunlock;
Spoiler: there is a race condition here that
sleep_for(getEatTime());
leads to deadlock – deadlock occurs when
cout << oslock << id << " all done eating." << endl
multiple threads are all blocked, waiting on a
<< osunlock;
left.unlock(); resource owned by one of the other blocked
right.unlock(); threads. When could this happen?
} 10
Food For Thought
What if: all philosophers grab their left fork and then go off the CPU?
• Deadlock! All philosophers will wait on their right fork, which will never
become available
• Testing our hypothesis: insert a sleep_for call in between grabbing the two
forks
• We should be able to insert a sleep_for call anywhere in a thread routine and
have no concurrency issues.
• We (incorrectly) assumed that at least one philosopher is always able to pick
up both of their forks. How can we fix this? Need to limit number of
philosophers that try to pick up a fork.

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.

How can we encode this into our program?


Have a counter of “permits”. Initially 4. A philosopher must have a permit
(decrement counter or wait) to try to eat. Once done eating, a philosopher
returns its permit (increment counter).

13
Tickets, Please…
int main(int argc, const char *argv[]) {
mutex forks[kNumForks];

size_t permits = kNumForks - 1;


mutex permitsLock;

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.

static void philosopher(size_t id, mutex& left, mutex&


right, size_t& permits, mutex& permitsLock) {
for (size_t i = 0; i < kNumMeals; i++) {
think(id);
eat(id, left, right, permits, permitsLock);
}
}

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.

static void grantPermission(size_t& permits, mutex&


permitsLock) {
permitsLock.lock();
permits++;
permitsLock.unlock();
}

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

static void waitForPermission(size_t& permits, mutex&


permitsLock) {
while (true) {
permitsLock.lock();
if (permits > 0) break;
permitsLock.unlock();
// wait a little while (how??)
}
permits--;
permitsLock.unlock();
18
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

static void waitForPermission(size_t& permits, mutex&


permitsLock) {
while (true) { This is called busy
permitsLock.lock();
if (permits > 0) break;
waiting (bad). We are
permitsLock.unlock(); unnecessarily and arbitrarily
sleep(??); using CPU time to check
} when a permit is available.
permits--;
permitsLock.unlock();
19
It would be nice if someone
could let us know when
they return their permit.
Then, we can sleep until
this happens.
20
Plan For Today
• Recap: mutexes and dining philosophers
• Encoding resource constraints
• Condition Variables

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

The event here is ”some permits are again available”.


24
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

We can check whether there are permits now


available by checking the permits count.
25
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

When someone returns a permit and there were


no permits available previously, notify all.
28
grantPermission
We must notify all once permits have become available again to wake up waiting
threads.

static void grantPermission(size_t& permits,


condition_variable_any& permitsCV, mutex& permitsLock) {
permitsLock.lock();
permits++;
if (permits == 1) permitsCV.notify_all();
permitsLock.unlock();
}
When someone returns a permit and there were no permits
available previously (meaning some people might be waiting),
notify all. (Side note: could we notify every time instead?)
29
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

If we need a permit but there are none available, wait.


30
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.

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();
} 31
grantPermission
Other threads need the lock to return permits:

static void grantPermission(size_t& permits,


condition_variable_any& permitsCV, mutex& permitsLock) {
permitsLock.lock();
permits++;
if (permits == 1) permitsCV.notify_all();
permitsLock.unlock();
}

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.

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();
} This is the idea for what we want to do – but
permits--; there are some additional cases/ quirks we
permitsLock.unlock(); need to account for.
} 33
waitForPermission (Final version)
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
while (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}

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();
}

z permits = 0 I need to wait for


z
z a permit in order
to eat.
PERMIT

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();
}

z permits = 0 I need to wait for


z
z a permit in order
to eat.
PERMIT

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();
}

All done eating! I permits = 0 z


will return my permit. z
z
PERMIT

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();
}

All done eating! I permits = 1 z


will return my permit. z
z

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();
}

Oh! I should notify permits = 1 z


that there is a z
z
permit now.

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();
}

“Attention waiting permits = 1 z


threads, a permit is z
z
available!”

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();
}

Solution: condition variables are meant for these situations.


• wait() takes a mutex as a parameter
• It will unlock the mutex for us after we are put to sleep.
• When we are notified, it will only return once it has reacquired the mutex for
us (in other words, waits for lock if already owned, and then returns).
48
Condition Variable Wait
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}

cv.wait() does the following:


1. it puts the caller to sleep and unlocks the given lock, all atomically
2. it wakes up when the cv is signaled
3. upon waking up, it tries to acquire the given lock (and blocks until it's able to do
so)
4. then, cv.wait returns 49
waitForPermission (In progress)
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();
}

Oh! I should notify permits = 1


that there is a z z
permit now. z z
z
z

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();
}

“Attention waiting permits = 1


threads, a permit is z z
available!” z z
z z

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

static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,


mutex& permitsLock) {
permitsLock.lock();
if (permits == 0) {
permitsCV.wait(permitsLock);
// by the time we wake up here, all the permits could already be gone!
}
permits--;
permitsLock.unlock();
}
63
waitForPermission (Final version)
static void waitForPermission(size_t& permits, condition_variable_any& permitsCV,
mutex& permitsLock) {
permitsLock.lock();
while (permits == 0) {
permitsCV.wait(permitsLock);
}
permits--;
permitsLock.unlock();
}

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.

Next time: more about race conditions

cp -r /afs/ir/class/cs111/lecture-code/lect14 . 68

You might also like