Critical Sections With Lots of Threads
Critical Sections With Lots of Threads
Announcements
CS 414 Homework due today
Hard to detect:
All possible schedules have to be safe
Number of possible schedule permutations is huge
Time
Processes progress with non-zero speed, no assumption on clock speed Used extensively in operating systems: Queues, shared variables, interrupt handlers, etc.
Solution Structure
Shared vars:
Initialization:
Process: ... ... Entry Section
Critical Section
Exit Section
Solution Requirements
Mutual Exclusion
Only one process can be in the critical section at any time
Progress
Decision on who enters CS cannot be indefinitely postponed
No deadlock
Bounded Waiting
Bound on #times others can enter CS, while I am waiting
No livelock
CSExit(int i) {
turn = J; inside[i] = false; }
CSExit(int i) {
inside[i] = false; }
Simple is good!!
CSExit(int i) {
inside[i] = false;
Bakery concept
Think of a popular store with a crowded counter, perhaps the pastry shop in Montreals fancy market
People take a ticket from a machine If nobody is waiting, tickets dont matter When several people are waiting, ticket order determines order in which they can make purchases
CSEnter(int i) {
ticket[i] = ++next_ticket; for(J = 0; J < N; J++) while(ticket[J] && ticket[J] < ticket[i]) continue;
CSExit(int i) {
ticket[i] = 0;
CSEnter(int i) {
CSExit(int i) {
ticket[i] = 0;
ticket[i] = max(ticket[0], ticket[N-1])+1; for(J = 0; J < N; J++) } while(ticket[J] && ticket[j] < ticket[i]) continue;
Clever idea: just add one to the max. Oops two could pick the same value!
CSEnter(int i) {
ticket[i] = max(ticket[0], ticket[N-1])+1; for(J = 0; J < N; J++) } while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue;
CSExit(int i) {
ticket[i] = 0;
Oops i could look at J when J is still storing its ticket, and yet J could have a lower id than me (i)!
CSEnter(int i) {
CSExit(int i) {
ticket[i] = 0;
choosing[i] = true; ticket[i] = max(ticket[0], ticket[N-1])+1; } choosing[i] = false; for(J = 0; J < N; J++) { while(choosing[J]) continue; while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue;
} }
CSEnter(int i) {
do { ticket[i] = 0; choosing[i] = true; ticket[i] = max(ticket[0], ticket[N-1])+1; choosing[i] = false; } while(ticket[i] >= MAXIMUM); for(J = 0; J < N; J++) { while(choosing[J]) continue; while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue;
CSExit(int i) {
ticket[i] = 0;
} }
Some systems disable interrupts briefly when calling CSEnter() and CSExit() Some use hardware help: atomic instructions
Assumes that test_and_set is compiled to a special hardware instruction that sets the lock and returns the OLD value (true: locked; false: unlocked)
Problem: Does not satisfy liveness (bounded waiting) (see book for correct solution)
test_and_set Instruction
Definition:
boolean test_and_set (boolean *target) { boolean rv = *target; *target = TRUE; return rv: }
Swap Instruction
Definition:
void Swap (boolean *a, boolean *b) { boolean temp = *a; *a = *b; *b = temp: }
Semaphores
Non-negative integer with atomic increment and decrement Integer S that (besides init) can only be modified by:
P(S) or S.wait(): decrement or block if already 0 V(S) or S.signal(): increment and wake up process if any
V(S) { S++; }
Semaphore Types
Counting Semaphores:
Any integer Used for synchronization
Binary Semaphores
Value is limited to 0 or 1 Used for mutual exclusion (mutex) Process i Shared: semaphore S Init: S = 1; P(S); Critical Section V(S);
Semaphore Implementation
Must guarantee that no two processes can execute P () and V () on the same semaphore at the same time
No process may be interrupted in the middle of these operations
Thus, implementation becomes the critical section problem where the P and V code are placed in the critical section.
Could now have busy waiting in critical section implementation
But implementation code is short Little busy waiting if critical section rarely occupied
Note that applications may spend lots of time in critical sections and therefore this is not a good solution.
Two operations:
block place the process invoking the operation on the appropriate waiting queue. wakeup remove one of processes in the waiting queue and place it in the ready queue.
Implementing Semaphores
Busy waiting (spinlocks)
Consumes CPU resources No context switch overhead
typedef struct semaphore { int value: ProcessList L; } Semaphore; void P(Semaphore *S) { S->value = S->value - 1; if (S.value < 0) { add this process to S.L; block(); } }
Spin for as long as block cost If lock not available, then block void V(Semaphore *S) { S->value = S->value + 1; Shown factor of 2-optimal! if (S->value <= 0) { remove process P from S.L; wakeup P } }
Implementing Semaphores
Per-semaphore list of processes
Implemented using PCB link field Queuing Strategy: FIFO works fine
Will LIFO work?
In Summary
Fundamental Issue
Programmers atomic operation is not done atomically Atomic Unit: instruction sequence guaranteed to execute indivisibly Also called critical section (CS)
Implementing Semaphores
Multithread synchronization algorithms shown earlier Could have a thread disable interrupts, put itself on a wait queue, then context switch to some other thread (an idle thread if needed) The O/S designer makes these decisions and the end user shouldnt need to know