Filip Jagodzinski
Announcements Homework 2 you have 2 weeks start now Book questions including two custom ones A single programming task File names and directories, class conventions homework2-script, or homework2-script.txt lab2-script, or lab2-script.txt
Announcements Homework 2 you have 2 weeks start now Book questions including two custom ones A single programming task File names and directories, class conventions homework2-script, or homework2-script.txt lab2-script, or lab2-script.txt Git (any repo) conventions Do not push.o or executables to a git repo why? Use.gitignore or add files one-by-one (Example shown right) file :.gitignore *.o *.exe *.pdf
Review Asynchronous cancellation Deferred cancellation A thread immediately terminates the target thread The target thread periodically checks in to find out if it should be terminated; if so, does it in an orderly fashion
Review Asynchronous cancellation Deferred cancellation A thread immediately terminates the target thread The target thread periodically checks in to find out if it should be terminated; if so, does it in an orderly fashion Q: Why might asynchronous thread cancellation be problematic? buffer thread 1 thread 2
Review Thread B i1: count = count + 1 i2: count = count - 1
Review Thread B i1: count = count + 1 i2: count = count - 1 a1: load count a2: add 1 a3: store count Register/ALU view b1: load count b2: subtract 1 b3: store count
Review Thread B i1: count = count + 1 i2: count = count - 1 a1: load count a2: add 1 a3: store count Register/ALU view b1: load count b2: subtract 1 b3: store count Assume initial value of count = 4 a1 < b1 < a2 < a3 < b2 < b3 count = 3 a1 < a2 < a3 < b1 < b2 < b3 count = 4 a1 < a2 < b1 < b2 < b3 < a3 count = 5 b1 < b2 < a1 < b3 < a2 < a3 count = 5
Review Peterson s Solution do { flag[i] = true; turn = j; while (flag[j] && turn == j){}; Critical section flag[i] = false // other stuff (remainder) } while(true); Works for 2 processes/threads j = 1-i; Requires additional (shared) data : int turn; boolean flag[2]; Process i sets flag[i] to true (to specify, hey, I m ready to entry my critical section ) A process sets turn to j (the other process) If both processes try to enter their critical section, although flag might be [1,1], because turn is a single shared value, only one of the processes will succeed in setting turn to the other process.
Review Most operating systems allow, via a system call, the use of a mutex this allows the application programmer to solve a critical section problem Mutex : Mutual Exclusion This high level idea is the following : have the OS provide system calls for a lock that can be used to control access to a critical section. do { // acquire lock Critical section acquire(){ while(!available) {}; available = false } // release lock // other stuff (remainder) } while(true); release(){ available = true } available, in this implementation, is the variable that is used to specify whether a process is in its critical section
In-class exercise
Review A solution to the critical section problem must satisfy 3 criteria Mutual exclusion : if a process is executing its critical section, no other process can be executing its critical section Progress : If no process is executing its critical section, AND some process wants to enter its critical section, then only those processes NOT executing code in their other sections can decide who enters Bounded Waiting : there must be a limit on the number of times that ANOTHER process is allowed to enter its critical section after a process has made a request to enter its critical section
Today Chapter 5 s Deadlock
Synchronization When more than 1 thread is running, synchronization is important Some terminology, assuming two events : Serialization : Event A must happen before? Event B Mutual Exclusion : Events A and B must NOT happen? at the same time Concurrent :?
Synchronization When more than 1 thread is running, synchronization is important Some terminology, assuming two events : Serialization : Event A must happen before Event B Mutual Exclusion : Events A and B must NOT happen? at the same time Concurrent :?
Synchronization When more than 1 thread is running, synchronization is important Some terminology, assuming two events : Serialization : Event A must happen before Event B Mutual Exclusion : Events A and B must NOT happen at the same time Concurrent :?
Synchronization When more than 1 thread is running, synchronization is important Some terminology, assuming two events : Serialization : Event A must happen before Event B Mutual Exclusion : Events A and B must NOT happen at the same time Concurrent : More than 1 thread making progress... it is not possible to know a priori what instruction history will be realized
Instruction Execution Q: How might you enforce a certain order of instruction execution?
Instruction Execution Q: How might you enforce a certain order of instruction execution?
Instruction Execution A1 A2 A3 Thread M M1 M2 M3 Q: What are the possible orderings, histories, of A1-A3 and M1-M3 if threads A and M are run concurrently? Q: How many unique execution histories are there in this scenario?
Instruction Execution A1 A2 A3 Thread M M1 M2 M3 What if only one the below two execution histories were acceptable Desired execution order : A1 < A2 < M1 < M2 < M3 < A3 Desired execution order : A1 < M1 < A2 < M2 < M3 < A3 Q: How would you impose these orders using sleep?
Instruction Execution A1 A2 A3 Thread M M1 M2 M3 What if only one the below two execution histories were acceptable Desired execution order : A1 < A2 < M1 < M2 < M3 < A3 Desired execution order : A1 < M1 < A2 < M2 < M3 < A3 Q: Is using sleep() a practical solution?
Instruction Execution Q: How is it done in the real world?
Instruction Execution Q: How is it done in the real world? A is a data structure that permits threads to coordinate among each other and specify which thread(s) wait and which thread(s) execute. Invented by Edsger Dijkstra (for CS applications) semaphore : a system of sending messages
s A data structure that contains only a single non-negative integer as a datum int value
s A data structure that contains only a single non-negative integer as a datum The integer can be initialized to any nonnegative value, but once declared and set, it is modified only by several methods (there are no direct setter and getter methods) int value Q: Why are there no setter and getter methods for semaphores?
s A data structure that contains only a single non-negative integer as a datum The integer can be initialized to any nonnegative value, but once declared and set, it is modified only by several methods (there are no direct setter and getter methods) Operation 1 : increment Operation 2 : decrement int value + (int) + increment + decrement
s A data structure that contains only a single non-negative integer as a datum The integer can be initialized to any nonnegative value, but once declared and set, it is modified only by several methods (there are no direct setter and getter methods) Operation 1 : increment Operation 2 : decrement The method is the constructor; it creates and returns a reference variable to a new int value + (int) + increment + decrement
s A data structure that contains only a single non-negative integer as a datum The integer can be initialized to any nonnegative value, but once declared and set, it is modified only by several methods (there are no direct setter and getter methods) Operation 1 : increment Operation 2 : decrement The method is the constructor; it creates and returns a reference variable to a new int value + (int) + increment + decrement Q: Now that we have the structure, what is the behavior of the? Or, how is it used?
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore int value + (int) + increment + decrement
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore int value + (int) + increment + decrement Thread notifies the scheduler that it cannot proceed. Scheduler will prevent the thread from running until an event occurs that causes the thread to become unblocked
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement Sometimes referred to as waking
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement Q: Which thread is executed after a shared s value is incremented?
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement Q: Which thread is executed after a shared s value is incremented? A: Both the thread that is unblocked AND the thread that issued the increment are scheduled concurrently... Q: In which order?
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement When a thread signals a, it has no knowledge of how many other threads (if any) are waiting
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement The nuances of semaphores results in several unique use scenarios
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement The nuances of semaphores results in several unique use scenarios Incrementing may affect other threads Decrementing directly affects only the thread that issued the call
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement The nuances of semaphores results in several unique use scenarios Incrementing is often referred to as signal() Decrementing is often referred to as wait()
s If decrement would result in the datum being negative, then the thread that issued the decrement will be blocked and will not continue until ANOTHER thread increments the semaphore If increment occurs, an already waiting thread is unblocked. int value + (int) + increment + decrement Increment and decrement refer to what a DOES (to the value) Signal and Wait describe what they are USED FOR Dijkstra, because of this confusion referred to increment as V, and decrement as P use a meaningless name rather than a confusing one
s The data structure shown right is the user (software) view. int value + (int) + increment + decrement
s The data structure shown right is the user (software) view. When a semaphore is created by the OS, the object has additional information there is needed a list of processes/threads that have invoked the decrement method, and which are blocked typedef struct{ int value; struct process *list; } semaphore int value + (int) + increment + decrement int value - process *list + increment + decrement
s Syntax is straight-forward but it is OS specific, so the pseudocode description is used in most textbooks asem = (3) asem.increment() asem.decrement()
s Syntax is straight-forward but it is OS specific, so the pseudocode description is used in most textbooks asem = (3) asem.increment() asem.decrement() Task : Draw the variable, object reference diagram that results after this line of code is executed
s Syntax is straight-forward but it is OS specific, so the pseudocode description is used in most textbooks asem = (3) asem.increment() asem.decrement() asem value = 3
s Syntax is straight-forward but it is OS specific, so the pseudocode description is used in most textbooks asem = (3) asem.increment() asem.decrement() asem value = 4 Q: When asem s value is incremented from 3 to to 4, what effect does that have on?
s Syntax is straight-forward but it is OS specific, so the pseudocode description is used in most textbooks asem = (3) asem.increment() asem.decrement() asem value = 4 Q: When asem s value is incremented from 3 to to 4, what effect does that have on? Q: When asem s value is incremented to 4, what effect does that have on other threads?
s Syntax is straight-forward but it is OS specific, so the pseudocode description is used in most textbooks asem = (3) asem.increment() asem.decrement() asem value = 3 Q: When asem s value is incremented from 3 to to 4, what effect does that have on? Q: When asem s value is incremented to 4, what effect does that have on other threads? Q: When asem s value is decremented from 4 to 3, what effect does that have on and on other threads?
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) Q: What is achieved when the code in the yellow box is executed?
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 0
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 0 Q: What is achieved when the code in the yellow box is executed?
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 1
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 1 Q: What is achieved when the code in the yellow box is executed?
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 0
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 0 Q: What is achieved when the code in the yellow box is executed?
s Another example mysem = (0) mysem.increment() mysem.decrement() mysem.decrement() mysem.decrement() print( hello ) mysem value = 0 self stalls is the last decrement executed? Is the print statement executed?
s s are used to halt a thread and to enforce running one thread in a specific sequence relative to another
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) In-class exercise 1 Thread B a1 b1
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Task : Explain via prose how to use a semaphore(s) to attain this goal Thread B a1 b1
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) Thread B a1 b1
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) a1 sem.increment() Thread B sem.decrement() b1 Task : Be able to explain why the above use of the ensures the goal
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) a1 sem.increment() Thread B sem.decrement() b1 Q: Does the use of the by the two threads guarantee that will complete prior to Thread B starting?
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) a1 sem.increment() Thread B sem.decrement() b1 We do not know how the OS will schedule the two threads. Q: What are the scheduling choices?
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) a1 sem.increment() Thread B sem.decrement() b1 Choice 1 : < Thread B Choice 2 : Thread B < Task : Be sure you understand both execution orders
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) a1 sem.increment() Thread B sem.decrement() b1 Choice 1 : < Thread B Choice 2 : Thread B < Q: What Is the sequence of events when Thread A is scheduled to execute first? (on the board walk through)
s Assume a1 writes to a file, and b1 prints a line from the file (hence reads from the file) Goal : We want a1 to complete before b1 begins Already run code sem = (0) a1 sem.increment() Thread B sem.decrement() b1 Choice 1 : < Thread B Choice 2 : Thread B < Q: What Is the sequence of events when Thread B is scheduled to execute first? (on the board walk through)
s In-class exercise 2 a1 a2 Thread B b1 b2 Goals a1 must happen before b2 b1 must happen before a2
s Sample solution (in class) Already run code Thread B a1 b1 a2 b2 Goals a1 must happen before b2 b1 must happen before a2
s Sample solution (in class) Already run code Thread B a1 b1 a2 b2 Goals a1 must happen before b2 b1 must happen before a2 Q : How many unique solutions are there? Q : What is the fewest number of semaphores needed?
Deadlock sem1 = (0) sem2 = (0) a1 sem1.increment() sem2.decrement() a2 Thread B b1 sem2.increment() sem1.decrement() b2 0 0 We saw that the above use of two semaphores enforced that a1 happen before b2, and b1 happen before a2 Q: Is use of semaphores always safe?
Deadlock Thread B sem1 = (0) sem2 = (0) a1 a2 b1 b2 0 0 We saw that the above use of two semaphores enforced that a1 happen before b2, and b1 happen before a2 Q: Is use of semaphores always safe? Task : Add method calls to sem1 and sem2 such that and Thread B do not execute to completion.
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first?
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first? a1
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first? a1 sem1 -> -1 (attempt, A self blocks)
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first? a1 sem1 -> -1 (attempt, A self blocks) b1
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first? a1 sem1 -> -1 (attempt, A self blocks) b1 sem2 -> -1 (attempt, B self blocks)
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first? a1 sem1 -> -1 (attempt, A self blocks) b1 sem2 -> -1 (attempt, B self blocks) Q: At this point what happens?
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 < Thread B Q: What is the result of the execution of the two Threads, assuming A is scheduled first? a1 sem1 -> -1 (attempt, A self blocks) b1 sem2 -> -1 (attempt, B self blocks) Deadlock
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 Thread B < Q: What is the result of the execution of the two Threads, assuming B is scheduled first?
Deadlock sem1 = (0) sem2 = (0) a1 sem1.decrement() sem2.increment() a2 Thread B b1 sem2.decrement() sem1.increment() b2 0 0 Thread B < Q: What is the result of the execution of the two Threads, assuming B is scheduled first? Also a Deadlock
Mutex Thread B count = count + 1 count = count +1 Add semaphores to the above two threads to enforce mutual exclusion to the shared variable count Hint : This can be implemented with a single semaphore Unlike previously, we don t care which of the two threads is scheduled first Further assume that we do NOT want to decompose the above 2 instructions into fetch, update, and WB steps
Mutex mutex = (1) mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() Assume the above decrement and increment invocations are placed among 2 threads, and B, run concurrently. Q: What value should the semaphore mutex be initialized to so that its use solves the critical section problem for Threads A and B? A : -1 B : 0 C : 1 D : 2
Mutex mutex = (1) 1 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() Q: What are the possible sequences of scheduling/execution?
Mutex mutex = (1) 1 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Q: What are the possible sequences of scheduling/execution? Thread B <
Mutex mutex = (1) 0 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 Q: Does this self-block A?
Mutex mutex = (1) 0 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 Q: Because of duplicated pipelines, threading, etc., and Thread B are being executed concurrently at this point, what happens if B for some reason is scheduled?
Mutex mutex = (1) 0 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 B decrements : mutex -> -1 (attempt, blocks)
Mutex mutex = (1) 0 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 B decrements : mutex -> -1 (attempt, blocks) A updates count
Mutex mutex = (1) 0 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 B decrements : mutex -> -1 (attempt, blocks) A updates count A increments : mutex -> 0 (B unblocked)
Mutex mutex = (1) 0 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 B decrements : mutex -> -1 (attempt, blocks) A updates count A increments : mutex -> 0 (B unblocked) B updates count
Mutex mutex = (1) 1 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < A decrements : mutex -> 0 B decrements : mutex -> -1 (self blocks) A updates count A increments : mutex -> 0 (B unblocked) B updates count B increments : mutex -> 1
Mutex mutex = (1) 1 mutex.decrement() count = count + 1 mutex.increment() Thread B mutex.decrement() count = count +1 mutex.increment() < Thread B Thread B < Task : Be able to step through the same code for the other sequence
Mutex Q: What is the utility of using semaphores to impose mutex versus the use of Peterson s solution? Q: Does the use of semaphores satisfy all of the conditions needed for a solution to the critical section problem?
Mutex Thread B Thread C count = count + 1 count = count +1 count = count +1 Q: What is the minimum number of semaphores needed to enforce mutual exclusion among these three threads, and what should the semaphore(s) be initialized to? A : A single semaphore, initialized to 0 B : Two semaphores, both initialized to 0 C : Three semaphores, each initialized to 0 D : None of the above
Mutex mutex.wait() count = count + 1 mutex.signal() Thread B mutex.wait() count = count +1 mutex.signal() Thread C mutex.wait() count = count +1 mutex.signal() Q: What is the minimum number of semaphores needed to enforce mutual exclusion among these three threads, and what should the semaphore(s) be initialized to? A : A single semaphore, initialized to 0 B : Two semaphores, both initialized to 0 C : Three semaphores, each initialized to 0 D : None of the above A single semaphore, initialized to 1
Multiplex Q: What is one functional purpose for initializing a semaphore to a non-zero value when 1, 2, 3, or n threads use that semaphore? a = (8) 8
Multiplex Thread B x = x + 1 x = x - 12 Thread C x = 4 Create one or more semaphores and invoke the wait and signal methods among Threads A-C to enforce an upper limit of 2 for the number of threads that can access concurrently the shared variable x
Multiplex multiplex = (2) 2 multiplex.dec() x = x + 1 multiplex.inc() Thread B multiplex.dec() x = x 12 multiplex.inc() Thread C multiplex.dec() x = 4 multiplex.inc() Q: How many possible choices are there of scheduling the 3 Threads?
Multiplex multiplex = (2) 2 multiplex.dec() x = x + 1 multiplex.inc() Thread B multiplex.dec() x = x 12 multiplex.inc() Thread C multiplex.dec() x = 4 multiplex.inc() Q: How many possible choices are there of scheduling the 3 Threads? < Thread B < Thread C < Thread C < Thread B Thread B < < Thread C Thread B < Thread C < Thread C < < Thread B Thread C < Thread B <
Multiplex multiplex = (2) 2 multiplex.dec() x = x + 1 multiplex.inc() Thread B multiplex.dec() x = x 12 multiplex.inc() Thread C multiplex.dec() x = 4 multiplex.inc() Q: How many possible choices are there of scheduling the 3 Threads? < Thread B < Thread C < Thread C < Thread B Thread B < < Thread C Thread B < Thread C < Thread C < < Thread B Thread C < Thread B < Regardless of the scheduling, only 2 Threads at any one time may access the code that updates the value of x.
Up Next Implementation of Mutex using blitz Chapter 5 : Monitors Chapter 6 : Scheduling