11/18/20151 operating systems design (cs 423) elsa l gunter 2112 sc, uiuc based on slides by roy...
TRANSCRIPT
04/20/23 1
Operating Systems Design (CS 423)
Elsa L Gunter
2112 SC, UIUC
http://www.cs.illinois.edu/class/cs423/
Based on slides by Roy Campbell, Sam King, and Andrew S Tanenbaum
Synchronizing multiple threads
Must control interleaving between threads Order of some operations irrelevant
Independent Other operations are dependent and order
does matter All possible interleaving must yield a
correct answer A correct concurrent program will work no
matter how fast the processors are that execute the various threads
04/20/23 2
Synchronizing multiple threads
All interleavings result in correct answer
Try to constrain the thread executions as little as possible
Controlling the execution and order of threads is called “synchronization”
04/20/23 3
Too much milk
The Gunter household drinks a lot of milk, but has a small fridge
Problem: Carl and Elsa want there to always at least one gallon of milk in the fridge for dinner; fridge holds at most two gallons
If either sees there is less than one gallon, goes to buy milk
Specification: Someone buys milk if running low
Never more than two gallons of milk milk
04/20/23 4
Solution #0 – no sync
Carl Elsa5:30 Comes home5:35 Checks milk5:40 Goes to store5:45 Comes home5:50 Buys milk Checks milk5:55 Goes home Goes to store6:00 Puts milk in Fridge Buys milk6:05 Comes home6:10 Too much milk!
04/20/23 5
Mutual Exclusion
Ensure that only 1 thread is doing a certain thing at one time Only one person goes shopping at one time
Critical section A section of code that needs to run atomically w.r.t.
other code If code A and code B are critical sections w.r.t. each
other threads cannot interleave events from A and B
Critical sections must be atomic w.r.t. each other Share data (or other resourced, e.g., screen, fridge)
What is the critical section in solution #0?
04/20/23 6
Too much milk (solution #1)
Assume only atomic operations are load and store Idea: leave note that going to check on milk status Carl: if (no note)
{if (milk low) {leave note; buy milk; remove note;} Elsa: if (no note)
{if (milk low) {leave note; buy milk; remove note;} What can go wrong? Is this better than before?
04/20/23 7
Too much milk (solution #2)
Idea: Change order of leave note and check milk
Carl: if (milk low){if (no note) {leave note; buy milk; remove note;}
Elsa: if (milk low){if (no note) {leave note; buy milk; remove note;}
What can go wrong? Is this better than before?
04/20/23 8
Too much milk (solution #3)
Idea: Protect more actions with note Carl: if (no note) {leave note;
if (milk low) {buy milk}; remove note;} Elsa: if (no note) {leave note; if (milk low)
{buy milk}; remove note;} What can go wrong? Is this better than before?
04/20/23 9
Too much milk (solution #4)
Idea: Change order of leaving note and checking note
Carl: leave noteCarl; if (no noteElsa) {if (milk low) {buy milk};} ; remove noteCarl
Elsa: leave noteElsa; if (no noteCarl) {if (milk low) {buy milk};} ; remove noteElsa
What can go wrong? Is this better than before?
04/20/23 10
Too much milk (solution #5)
Idea: When both leave note, always give priority to fixed one to buy milk
Carl: leave noteCarl; while (noteElsa) {do nothing};if (milk low) {buy milk}; remove noteCarl
Elsa: leave noteElsa; if (no noteCarl) {if (milk low) {buy milk};} ; remove noteElsa
Simplified instance of Bakery Algorithm
04/20/23 11
Too much milk (solution #5)
while (noteElsa) for Carl prevents him from buying milk at same time as Elsa
Proof of correctness Two parts: Will never have two people
buying milk at same time Will always have someone able to buy milk
if it is needed Correct, but ugly
Complicated, Asymmetric, Inefficient Carl wastes time while waiting (Busy Waiting)
04/20/23 12
Higher-level synchronization
Problem: could solve “too much milk” using atomic loads/stores, but messy
Solution: raise the level of abstraction to make life easier for the programmer
04/20/23 13
Concurrent programsConcurrent programsHigh-level
synchronization provided by software
High-level synchronization provided
by softwareLow-level atomic operations provided by
hardware
Low-level atomic operations provided by
hardware
Locks (mutexes)
A lock is used to prevent another thread from entering a critical section
Two operations Lock(): wait until lock is free, then acquire do {if (lock == LOCK_FREE)
{ lock = LOCK_SET;
break;} }
while(1) Unlock(): lock = LOCK_FREE
04/20/23 14
Locks (mutexes)
Why was the “note” in Too Much Milk solutions #1 and #2 not a good lock?
Four elements of using locks Lock is initialized to be free Acquire lock before entering a critical section Wait to acquire lock if another thread already
holds Release lock after exiting critical section
All synchronization involves waiting Thread can be running, or blocked
(waiting)
04/20/23 15
Locks
Locks -- shared variable among all thread
Multiple threads share locks Only affects threads that try to acquire
locks Important: Lock acquisition is atomic!
04/20/23 16
Lock Variables
Critical section -- part of the program where threads access shared (global) state
Locks -- shared variables used to enforce mutual exclusion Can have multiple lock variables
04/20/23 17
Locks (mutexes)
Locks make “Too Much Milk” really easy to solve!
Correct but inefficient How to reduce waiting for lock? How to reduce time lock is held?
04/20/23 18
Elsa:lock(frigdelock);if (milk low) {buy milk}unlock(fridgelock)
Carl:lock(frigdelock);if (milk low) {buy milk}unlock(fridgelock)
Too Much Milk – Solution 7
Does the following work?lock();if (milk low & no note) { leave note; unlock(); buy milk; remove note; }else { unlock() }
04/20/23 19
Too Much Milk – Solution 7
Does the following work?lock();if (milk low & no note) { leave note; unlock(); buy milk; lock(); remove note; unlock(); }else { unlock() }
04/20/23 20
Queues without Locks
enqueue (new_element, head) {
// find tail of queue
for(ptr=head; ptr->next != NULL;
ptr = ptr->next);
// add new element to tail
ptr->next = new_element;
new_element->next = NULL;
}
04/20/23 21
Queues without Locks
dequeue(head, element) {
element = NULL;
// if something on queue, remove it
if(head->next != NULL) {
element = head->next;
head->next = head->next->next;}
return element;
} What bad things can happen if two threads
manipulate the queue at the same time?
04/20/23 22
Thread-safe Queues with Locks
enqueue (new_elt, head) {
lock(queuelock);
// find tail of queue
for(ptr=head; ptr->next != NULL;
ptr = ptr->next);
// add new element to tail
ptr->next = new_elt;
new_elt->next = NULL;
unlock(queuelock);
}
dequeue(head, elt) {
lock(queuelock);
element = NULL;
// remove if possible
if(head->next != NULL) {
elt = head->next;
head->next =
head->next->next;}
unlock(queuelock);
return elt;
}
04/20/23 23
Invariants for multi-threaded queue
Can enqueue() unlock anywhere?
Stable state called an invariant I.e., something that is “always” true
Is the invariant ever allowed to be false?
04/20/23 24
Invariants for multi-threaded queue
In general, must hold lock when manipulating shared data
What if you’re only reading shared data?
04/20/23 25
Enqueue – Can we do better?
enqueue() {
lock
find tail of queue
unlock
lock
add new element to tail of queue
unlock
} Is this better?
04/20/23 26
Dequeue if empty?
What if you wanted to have dequeue() wait if the queue is empty?
Could spin in a loop:dequeue() {
lock(queuelock);
element = NULL;
while (head-next == NULL) {wait;};
if(head->next != NULL) {
element = head->next;
head->next = head->next->next;}
unlock(queuelock);
return element;
}
04/20/23 27
Problem
lock(queuelock);
…
while (head-next == NULL) {wait;};
Holding lock while waiting No one else can access list (if they
observe the lock) Wait forever
04/20/23 28
Dequeue if empty – Try 2
Could release the lock before spinning:lock(queuelock);
…
unlock(queuelock);
while (head-next == NULL)
{wait;};
Will this work?
04/20/23 29
Dequeue if empty – Try 3
Could release lock and acquire lock on every iteration
lock(queuelock);
…
while (head-next == NULL)
{unlock(queuelock);
lock(queuelock);}; This will work, but very ineffecient.
04/20/23 30