Download presentation
Presentation is loading. Please wait.
1
Chapter 5: Process Synchronization
Rev. by Kyungeun Park, 2017
2
Chapter 5 : Process Synchronization
Background The Critical-Section Problem Peterson’s Solution Synchronization Hardware Mutex Locks Semaphores Classic Problems of Synchronization Monitors Synchronization Examples Alternative Approaches
3
Objectives To introduce the critical-section problem, whose solutions can be used to ensure the consistency of shared data To present both software and hardware solutions of the critical-section problem To examine several critical process-synchronization problems To explore several tools that are used to solve process synchronization problems
4
Why Process Synchronization?
A cooperating process is one that can affect or be affected by other processes executing in the system. Expect sharing among cooperating processes: Direct sharing of a logical address space for code and data through the use of threads (refer to Chapter 4) Through files or messages for data Concurrent access to shared data may result in data inconsistency. Need mechanisms to ensure the orderly execution of cooperating processes that share a logical address space.
5
Background Processes can execute concurrently or in parallel:
May be interrupted at any time, partially completing execution Issues involving the integrity of data shared by several processes Illustration of the problem: Suppose that we wanted to provide a solution to the consumer-producer problem that fills all the buffers. We can do so by having an integer counter that keeps track of the number of full buffers. Initially, counter is set to 0. It is incremented by the producer after it produces a new buffer and is decremented by the consumer after it consumes a buffer.
6
Bounded-Buffer – Shared-Memory Solution
How concurrent or parallel execution affects the integrity of data shared by several processes? Shared buffer: a circular array with two logical pointers: in and out #define BUFFER_SIZE 10 typedef struct { . . . } item; item buffer[BUFFER_SIZE]; int in = 0; /* the next free position in the buffer */ int out = 0; /* the first full position in the buffer */ in Initial Empty Buffer (size 10) 1 2 9 out
7
Producer while (true) { /* Produce an item and put in nextProduced */
while (counter == BUFFER_SIZE) ; // do nothing buffer [in] = nextProduced; in = (in + 1) % BUFFER_SIZE; counter++; } * counter++ could be implemented in machine language as register1 = counter register1 = register counter = register1
8
Consumer while (true) { while (counter == 0) ; // do nothing
nextConsumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; counter--; /* Consume the item in nextConsumed */ } * counter-- could be implemented in machine language as register2 = counter register2 = register counter = register2
9
Race Condition Both the producer and consumer routines may not function correctly when executed concurrently. Consider this execution interleaving with “counter = 5” initially: S0: producer execute register1 = counter {register1 = 5} S1: producer execute register1 = register {register1 = 6} S2: consumer execute register2 = counter {register2 = 5} S3: consumer execute register2 = register {register2 = 4} S4: producer execute counter = register {counter = 6 } S5: consumer execute counter = register {counter = 4} Race Condition : a situation where several processes access and manipulate the same data concurrently and the outcome of the execution depends on the particular order in which the access takes place. Ensure that only one process at a time can manipulate the variable counter. Process synchronization and coordination among cooperating processes
10
Critical Section Problem
Consider system of n processes {p0, p1, … pn-1} Each process has a segment of code, critical section, where Process may be changing common variables, updating a table, writing a file, etc. When one process is executing in critical section, no other process is allowed to execute in its critical section. Critical section problem solving is to design a protocol that the processes can use to cooperate. Each process must ask permission to enter its critical section in the entry section. Followed by an exit section, then remainder section. Kernel code: a list of all open files maintained by an operating system data structures for maintaining memory allocation process lists or for handling interrupts race conditions frequently occur in operating systems
11
Critical Section General structure of process pi is do {
remainder section } while (TRUE) entry section exit section
12
Solution to Critical-Section Problem (1)
Three requirements to the solution: 1. Mutual Exclusion - If process Pi is executing in its critical section, then no other processes can be executing in their critical sections. 2. Progress - If no process is executing in its critical section and there exist some processes that wish to enter their critical section, then only those processes that are not executing in their remainder sections can participate in deciding which will enter its critical section next, and this selection cannot be postponed indefinitely. 3. Bounded Waiting - There exists a bound, or limit on the number of times that other processes are allowed to enter their critical sections after a process has made a request to enter its critical section and before that request is granted. Note Assume that each process executes at a nonzero speed No assumption concerning the relative speed of the n processes
13
Two General Approaches to Handle CSs
Two general approaches used to handle critical sections in operating systems Non-preemptive kernel – a process runs until it exits kernel mode, blocks, or voluntarily yields CPU Essentially free from race conditions on kernel data structures: only one process is active in the kernel at a time. Preemptive kernel – allows preemption of process when running in kernel mode So, carefully design to ensure that shared kernel data are free from race conditions Especially difficult to design for SMP architectures Ex) Two kernel-mode processes running simultaneously on different processors Then why favoring preemptive kernel over non-preemptive one? preemptive kernel : complicated, but more responsive, reducing exclusive occupation of the CPU more suitable for real-time programming allowing a real-time process to preempt a process currently running in the kernel
14
Peterson’s Solution Peterson’s solution: a classic software-based solution to the critical-section problem Worth reviewing the underlying algorithm to solve the critical section problem and the way of reasoning how to satisfy three requirements (mutual exclusion, progress, and bounded waiting) Restricted to two processes, P0 and P1, that alternate execution between their critical sections and remainder sections No guarantee to work on modern computer architecture Two processes share two data items: int turn; boolean flag[2];
15
Algorithm for Process Pi
do { flag[i] = true; turn = j; while (flag[j] && turn == j); critical section flag[i] = false; remainder section } while (TRUE); * at Process Pi do { flag[j] = true; turn = i; while (flag[i] && turn == i); critical section flag[j] = false; remainder section } while (TRUE); * at Process Pj The flag array is used to indicate if a process is ready to enter the critical section. flag[i] = true implies that process Pi is ready! The variable turn indicates whose turn it is to enter the critical section if both processes try to enter at the same time, turn will be set to both i and j at roughly the same time only one of these assignments will last the value of turn determines which of the two processes is allowed to enter its critical section first
16
Algorithm for Process Pi
do { flag[i] = true; turn = j; while (flag[j] && turn == j); critical section flag[i] = false; remainder section } while (TRUE); * at Process Pi Proof: assume both processes executes in their critical sections at the same time Mutual exclusion is preserved: When flag[j]=false or turn==i, each Pi enters its critical section. To exit while loop and enter their critical sections, as flag[i]=true and flag[j]=true (insufficient) So the value of turn should be 0 for P0 and 1 for P1 at the same time : impossible! If Pj has set flag[j] to true and is also executing in its while loop, then either turn==i or turn==j. If turn==i then Pi will enter the CS: If turn==j then Pj will enter the CS (ME). Progress requirement is satisfied: Bounded-waiting requirement is met: If Pj is not ready to enter the critical section, then flag[j]=false and Pi can enter its CS * If Pj exits its CS, it will reset flag[j] to false, allowing Pi to enter its CS. * If Pj resets flag[j] to true immediately, it must also set turn to i(Progress). Since no change on turn in Pi * Pi will enter its CS (Progress) after at most one entry by Pj (Bounded waiting). do { flag[j] = true; turn = i; while (flag[i] && turn == i); critical section flag[j] = false; remainder section } while (TRUE); * at Process Pj turn would be 0 or 1
17
Synchronization Hardware
Many systems provide hardware support for critical section code as well as software-based APIs All these solutions (from hardware to software-based APIs) based on the idea of locking Protecting critical regions through the use of locks In Single-processor environment – solved by preventing interrupts while modifying a shared variable Currently running code would execute without preemption No other instructions would be run, so no unexpected modifications to the shared variables Adopted by non-preemptive kernels: But, too inefficient on multiprocessor environments Disabling interrupt for all processors by sending the message: causes time consuming and delays Modern machines provide special atomic hardware instructions Atomic : non-interruptible, uninterruptible Allow us either to test memory word and set the value : TestAndSet( ) instruction Or to swap contents of two memory words : CompareAndSwap( ) instruction
18
TestAndSet() Instruction
Definition: run atomically as an uninterruptible instruction if two instructions are executed simultaneously on a different CPU, they will be executed sequentially in some order boolean TestAndSet (boolean *target) { boolean rv = *target; *target = TRUE; return rv: }
19
Solution using TestAndSet()
Shared boolean variable lock, initialized to FALSE Solution: do { while (TestAndSet (&lock )) /* run as an uninterruptible unit */ ; // do nothing // critical section lock = FALSE; // remainder section } while (TRUE);
20
CompareAndSwap() Instruction
Definition: operates on three operands. int CompareAndSwap (int *value, int expected, int new_value) { int temp = *value; if (*value == expected) *value = new value; return temp; }
21
Solution using CompareAndSwap()
Global variable, lock initialized to 0 The first process will set lock to 1 and enter its CS because lock == 0 inside CompareAndSwap() instruction. When the process exits the CS, it sets lock back to 0, allowing another process to enter its CS. Solution: do { // CompareAndSwap(&lock, 0, 1): only if lock==0, lock=1, returns original lock while (CompareAndSwap(&lock, 0, 1) != 0) ; /* do nothing */ // critical section lock = 0; // remainder section } while (TRUE); Both TestAndSet() and CompareAndSwap(): ME is met; But bounded waiting is not satisfied!
22
Bounded-waiting Mutual Exclusion with TestAndSet()
The two processes share two data items: boolean waiting[n]; // initialized to False boolean lock; // initialized to False Mutual Exclusion: Pi can enter its CS only if either waiting[i] == false or key == false key == false only if TestAndSet() executed ex) the first process of TestAndSet(), causing lock to be bound to this process waiting[i] == false if another process leaves its CS Progress: since a process exiting the CS either sets lock to false or sets waiting[j] to false, allowing a process waiting to enter its CS to proceed Bounded Waiting Time: a leaving process scans the array waiting in the cyclic ordering. Any waiting process will enter CS within n-1 times. do { // lock : false, waiting[n] : all false waiting[i] = true; key = true; while (waiting[i] && key) key = TestAndSet(&lock); // lock: false to true waiting[i] = false; // critical section j = (i + 1) % n; while ((j != i) && !waiting[j]) j = (j + 1) % n; if (j == i) // one cycle lock = false; // initial state else // within n-1 times waiting[j] = false; // take turns // remainder section } while (true);
23
Bounded-waiting Mutual Exclusion with TestAndSet()
do { waiting[i] = TRUE; key = TRUE; while (waiting[i] && key) key = TestAndSet(&lock); waiting[i] = FALSE; // critical section j = (i + 1) % n; while ((j != i) && !waiting[j]) j = (j + 1) % n; if (j == i) lock = FALSE; else waiting[j] = FALSE; // remainder section } while (TRUE); * at Process Pi do { waiting[j] = TRUE; key = TRUE; while (waiting[j] && key) key = TestAndSet(&lock); waiting[j] = FALSE; // critical section i = (j + 1) % n; while ((i != j) && !waiting[i]) i = (i + 1) % n; if (i == j) lock = FALSE; else waiting[i] = FALSE; // remainder section } while (TRUE); * at Process Pj
24
Mutex Locks Previous solutions are complicated and generally inaccessible to application programmers OS designers build software tools to solve critical section problem Simplest is mutex lock A process should acquire a lock before entering a critical section; It releases the lock when it exits the critical section. Protect critical regions and prevent race conditions: By first acquire() a lock, then release() it Boolean variable indicating if lock is available or not Calls to acquire() and release() must be atomic Usually implemented via hardware atomic instructions: using TestAndSet() or CompareAndSwap() Disadv.) But this solution requires busy waiting This lock therefore called a spinlock good for short term lock (No context-switch) Busy waiting wastes CPU cycles that some other processes use productively. do { acquire lock critical section release lock remainder section } while (true);
25
acquire() and release()
acquire() { while (!available) ; /* busy wait */ available = false; } release() { available = true; Solution to the critical section problem using mutex lock do { acquire lock critical section release lock remainder section } while (true);
26
Semaphore Previous hardware-based solutions: hard for application programmers to use. Synchronization tool, similar to a mutex lock, a semaphore is introduced. Semaphore S – integer variable Generally a counter used to provide access to a shared data object for multiple processes. Accessed only through two standard atomic operations: wait() and signal() wait() originally termed P, proberen, “to test” signal() originally called V, verhogen, “to increment” Less complicated Can only be accessed via two indivisible (atomic) operations wait (S) { while S <= 0; // no-op, busy wait S--; } signal (S) { S++;
27
Semaphore as General Synchronization Tool
Counting semaphore – integer value can range over an unrestricted domain Used to control access to a given resource consisting of a finite number of instances. The semaphore is initialized to the number of resources available. wait(S) when wishing to use a resource , signal(S) when releasing a resource Binary semaphore – integer value can range only between 0 and 1; Also known as mutex locks, initialized to 1 Can implement a counting semaphore S as a binary semaphore Provides mutual exclusion Semaphore mutex; // initialized to 1 do { wait (mutex); // Critical Section signal (mutex); // remainder section } while (TRUE);
28
Semaphore as a General Synchronization Tool
Synchronization problems Two concurrently running processes: P1 and P2 P1 with a statement S1 P2 with a statement S2 S2 to be executed only after S1 has completed Common semaphore synch, initialized to 0, for P1 and P2 S1; signal(synch); // in process P1 wait(synch); // in process P2 S2; Note: because synch is initialized to 0, P2 will execute S2 only after P1 has invoked signal(synch)
29
Characteristics of Semaphore
Resource sharing by a process: Test the semaphore that controls the resource Positive semaphore value use the resource, decrement by 1 0 semaphore value go to sleep until (semaphore > 0) Done with a shared resource increment by 1, awakening sleeping processes Main disadvantage of the semaphore definition is that it requires busy waiting. While a process is in its critical section, any other process that tries to enter its critical section must loop continuously in the entry code. Continual looping a problem in a multiprogramming system with a single CPU Busy waiting wastes CPU cycle that some other process might use. This type of semaphore is called a spinlock (spinning while waiting for the lock) Useful for multiprocessor systems as the alternative to the context switch when locks are expected to be held for short time.
30
Semaphore Implementation with no Busy waiting
To overcome the need for busy waiting: With each semaphore, there is an associated waiting queue, Modification of the definition of the wait() and signal() semaphore operations a process executes wait() and finds the semaphore is not positive wait and rather than busy waiting, block itself placing it into a waiting queue associated with the semaphore process state to waiting state control to the CPU scheduler resulting in selecting another process the process waiting on a semaphore should be restarted when some other process executes a signal() Two operations provided by the OS as basic system calls are additionally needed. block() – places the process invoking the operation on the appropriate waiting queue, suspending the process that invokes it. wakeup() – remove one of processes in the waiting queue and place it in the ready queue to restart it, changing from the waiting state to the ready state.
31
Semaphore Implementation with no Busy waiting
Each semaphore has two data items: value (of type integer) list : pointer to a list of processes (waiting queue) typedef struct { int value; // semaphore value struct process * list; // waiting queue } semaphore;
32
Revised Semaphore Implementation
Implementation of wait() : wait(semaphore *S) { Svalue--; // semaphore value may be negative, identifying # of if (Svalue < 0) { // processes waiting on that semaphore add this process to Slist; block(); // suspends the process that invokes it } Implementation of signal() : signal(semaphore *S) { Svalue++; if (Svalue <= 0) { remove a process P from Slist; wakeup(P); // resumes the execution of a blocked process P
33
Semaphore Implementation
Must guarantee that no two processes can execute wait () and signal () on the same semaphore at the same time Thus, implementation becomes the critical section problem where the wait and signal code are placed in the critical section In a single-processor environment, solved by disabling interrupts during the time the wait() and signal() operations are executing. In a multiprocessor environment, interrupts must be disabled on every processor. This is a difficult task and causes performance degradation. SMP system must provide alternative atomic locking techniques such as compare_and_swap() or spinlocks. Still, have busy waiting with wait() and signal() operations in critical section implementation Problem moved from the entry section to the critical sections of application programs with wait() and signal() operations But they are usually short, Critical section rarely occupied, and busy waiting occurs for short time Note a different situation with application programs whose CSs may be long Busy waiting is not a good solution.
34
Deadlock and Starvation
Semaphore with a waiting queue may result in indefinite waiting Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes. Definition (from Wiki ) A deadlock is the situation where a group of processes blocks forever because each of the processes is waiting for resources which are held by another process in the group. Let S and Q be two semaphores initialized to 1 P P1 wait (S); wait (Q); wait (Q); wait (S); … … signal (S); signal (Q); signal (Q); signal (S); Starvation – indefinite blocking (LIFO queue) A process may never be removed from the semaphore queue in which it is suspended Priority Inversion – Scheduling problem when lower-priority process holds a lock needed by higher-priority process (PL with R and preempted by PM and PH requesting R) Solved via priority-inheritance protocol (by inheriting the higher priority until the lower priority process finishes using resource R, needed by the higher-priority process.
35
Classical Problems of Synchronization
Classical problems used to test newly-proposed synchronization schemes, semaphores (mutex locks) Bounded-Buffer Problem Readers and Writers Problem Dining-Philosophers Problem
36
Bounded-Buffer Problem
N buffers, each can hold one item Semaphore mutex, initialized to the value 1, providing mutual exclusion for accesses to the buffer pool Semaphore full, initialized to the value 0, count the # of full buffers Semaphore empty, initialized to the value N, count the # of empty buffers
37
Bounded Buffer Problem with Semaphore
The structure of the producer process do { // produce an item // in next_produced wait (empty); wait (mutex); // add the item to the buffer signal (mutex); signal (full); } while (TRUE); The structure of the consumer process do { wait (full); wait (mutex); // remove an item // from buffer to next_consumed signal (mutex); signal (empty); // consume the item // in next_consumed } while (TRUE);
38
Readers-Writers Problem
A database is shared among several concurrent processes Readers – only read the database; they do not perform any updates Writers – can update (both read and write) the database Database access by reader(s) and writer(s), simultaneously causes chaos. Require that the writers have exclusive access to the shared database while writing to the database Synchronization problem: Readers-writers problem Problem – allow multiple readers to read at the same time Only one single writer can access the shared data exclusively Shared Data Semaphore mutex, initialized to 1, ensures mutual exclusion when read_count is updated Semaphore rw_mutex, initialized to 1, common to both reader and writer processes, functions as a mutual exclusion semaphore for the writers, and used by the first and last reader, and not used by readers while other readers are in their CS. Integer read_count, initialized to 0, keeps track of how many processes are currently reading the object.
39
Readers-Writers Problem (Cont.)
The structure of a writer process do { wait (rw_mutex) ; // writing is performed signal (rw_mutex) ; } while (TRUE); The structure of a reader process do { wait (mutex) ; // down to access read_count read_count++ ; if (read_count == 1) // if first process to read wait (rw_mutex) ; // wait until no write signal (mutex) // up for releasing read_count // reading is performed wait (mutex) ; // down mutex read_count-- ; if (read_count == 0) // if no more reader signal (rw_mutex) ; // allow a writer signal (mutex) ; // up mutex } while (TRUE);
40
Application of reader-writer lock on some systems
Reader-writer problem and its solutions have been generalized to provide reader-writer locks on some systems. Acquiring a reader-writer lock requires specifying the mode of the lock: either read or write access A process wishes only to read it requests the reader-writer lock in read mode Wishing to modify? request the lock in write mode Multiple processes are permitted to concurrently acquire a reader-writer lock in read mode Only one process may acquire the lock for writing Useful when it is easy to identify which one is reader or writer for shared data Useful for applications that have more readers than writers : increased concurrency compensates the overhead to implement reader-writer lock
41
Dining-Philosophers Problem
Each philosophers must alternately think and eat. Originally formulated in 1965 by E. Dijkstra as a competing problem for access to tape drive peripherals (Wiki) Don’t interact with their neighbors, occasionally try to pick up 2 (left and right) chopsticks (one at a time) to eat from bowl Need both to eat, then release both when done In the case of 5 philosophers Shared data Bowl of rice (data set): Infinite supply Semaphore chopstick [5], initialized to 1
42
Dining-Philosophers Problem Algorithm
The structure of Philosopher i: do { wait ( chopstick[i] ); // wait for left chopstick wait ( chopstick[ (i + 1) % 5] ); // wait for right chopstick // eat for a while signal ( chopstick[i] ); signal ( chopstick[ (i + 1) % 5] ); // think for a while } while (TRUE); What is the problem with this algorithm? Deadlock: simultaneously grab left chopstick and delayed forever Starvation?
43
Problems with Semaphores
Incorrect use of semaphore operations causes various types of errors Typical CS solution with semaphore (mutex, initialized to 1): wait (mutex) …. signal (mutex) what if signal (mutex) …. wait (mutex) ? ME violation because several processes in CS, but found only if it happens wait (mutex) … wait (mutex) Deadlock Omitting of wait (mutex) or signal (mutex) (or both) ME violation or Deadlock As a solution to this incorrect use of semaphores, high-level language constructs, the monitor type has been developed.
44
Monitors To deal with the previous errors, high-level abstraction that provides a convenient and effective mechanism to process synchronization A monitor type is an abstract data type, a collection of shared variables and operations (procedures) on those variables: only accessible by code within the procedure Provide mutual exclusion within the monitor Only one process (thread) at a time is active within the monitor No need to code the synchronization constraint explicitly. monitor monitor-name { // shared variable declarations procedure P1 (…) { …. } procedure Pn (…) {……} Initialization code (…) { … } } But not powerful enough to model some synchronization schemes Additional explicit synchronization mechanisms to write a tailor-made synchronization scheme Provided by the condition construct
45
Schematic view of a Monitor
46
General Overview of Monitor
A Monitor is: A programming language construct that controls access to shared data Compiler adds synchronization code A monitor encapsulates: Shared data structure Procedures Synchronization between concurrent procedure invocations A monitor guarantees mutual exclusion Only one thread can execute any monitor procedure at any time. If a second thread invokes a monitor procedure when a first thread is already executing one, it blocks on a wait queue outside the monitor. If a thread within a monitor blocks itself, another one can enter: the blocking thread waits inside the monitor.
47
Condition Variables Condition variables provide a mechanism to wait for events: A programming language construct that controls access to shared data Compiler adds synchronization code Condition variables support three operations: Wait: release monitor lock, wait for Condition Variable to be signaled Suspends the calling thread and releases the monitor lock. Called when condition is not true Signal: wakeup one waiting thread Resumes one thread waiting in wait() if any. Called when condition becomes true and wants to wake up one waiting thread Broadcast: wakeup all waiting thread Resumes all threads waiting in wait() Called when condition becomes true and wants to wake up all waiting threads
48
Bounded Buffer Problem with Monitor
Monitor BoundedBuffer { // shared variable declarations Item buffer[N] ; int in, out; Condition not_full; Condition not_empty; void producer(Item newItem) { while (buffer array is full) wait(not_full); Add newItem to buffer array; signal(not_empty); } Item consumer() { while (buffer array is empty) wait(not_empty); Get item from buffer array; signal(not_full); return item; Waiting to enter the Monitor Inactive threads waiting on condition variable queues Active thread Note: If there is no waiting thread on CV Queue, the signal is lost without keeping the history. Activated thread
49
Condition Variables Condition variables provides a method by which a monitor function can block its execution until it is signaled to continue Monitor condition variables condition x, y; // condition type variables Only two operations on a condition variable: x.wait () – a process invoking the operation is suspended until x.signal () is invoked Put executing process (thread) at the end of the condition variable queue Releases access to the monitor x.signal () – resumes exactly one suspended process (if any) that invoked x.wait () If no x.wait () on the variable, then it has no effect on the variable c.f. signal() associated with semaphores : always affect the state of semaphore
50
Monitor with Condition Variables
Monitor as one Big Lock
51
Monitor Behavior (1) Ref. for Monitor with semaphore:
52
Monitor Behavior (2) Ref. for Monitor with semaphore:
53
Monitor Behavior (3) Ref. for Monitor with semaphore:
54
Monitor Behavior (4) Ref. for Monitor with semaphore:
55
Monitor Behavior (5) Ref. for Monitor with semaphore:
56
Condition Variables Choices
If thread/process P invokes x.signal (), with Q in x.wait () state, what should happen next? Which thread/process to run inside the monitor? P (signaling thread) or Q (waiting thread)? If Q is resumed, then P must wait within the monitor. Otherwise, both P and Q processes would be active simultaneously within the monitor. Two possibilities exist: Signal and wait (Hoare semantics) – activate Q, P waits until Q leaves monitor or waits for another condition Difficult to implement Signal and continue (Mesa semantics) – Q waits (internally blocked state transitioned to a runnable state) until P leaves the monitor or waits for another condition Easy to implement, but before Q is activated, another thread comes in and run. Monitors implemented in Concurrent Pascal compromise P executing signal() immediately leaves the monitor. Hence Q is resumed. (signal_and_leave) Implemented in other languages including Java and C#.
57
Signaling on CV block After signaling on CV, immediately leave
(or execute) Ref. for Monitor with semaphore:
58
Dining-Philosophers Solution Using Monitors
Deadlock-free solution to the dining-philosophers problem Restriction: A philosopher may pick up her chopsticks only if both of them are available. Additional data structure to distinguish the philosophers’ state enum {THINKING, HUNGRY, EATING} state[5]; //Three states of a philosopher condition self[5]; Each philosopher i invokes the operations pickup() and putdown() in the following sequence: DiningPhilosophers.pickup(i); EAT DiningPhilosophers.putdown(i); No deadlock, but starvation is possible!
59
Solution to DP using Monitor(Cont.)
monitor DiningPhilosophers { enum { THINKING, HUNGRY, EATING } state [5] ; condition self [5]; void pickup (int i) void putdown (int i) void test (int i) void initialization_code() } Philosopher i must invoke the operations pickup() and putdown(): DiningPhilosophers.pickup(i); … eat DiningPhilosophers.putdown(i)
60
Solution to DP using Monitor(Cont.)
monitor DiningPhilosophers { enum { THINKING, HUNGRY, EATING } state [5] ; condition self [5]; void pickup (int i) { state[i] = HUNGRY; test(i); if (state[i] != EATING) self [i].wait(); // wait until // signaled to continue picking both chopsticks up } void putdown (int i) { state[i] = THINKING; // test left and right neighbors test((i + 4) % 5); test((i + 1) % 5); void test (int i) { if ( (state[(i + 4) % 5] != EATING) && (state[i] == HUNGRY) && (state[(i + 1) % 5] != EATING) ) { state[i] = EATING; self[i].signal(); // to make the // philosopher i continue picking up // operation } void initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING;
61
Monitor Implementation Using Semaphores
A programming language construct that controls access to shared data Compiler adds synchronization code
62
Monitor Implementation Using Semaphores
Variables semaphore mutex;// (initially = 1), provided for each monitor // used to control the number of processes semaphore next; // (initially = 0), as a waiting queue by processes // in the monitor after being released from a // condition’s queue by a signal operation // to suspend themselves and wait until the // resumed process either leaves or waits // (signal_and_wait) int next_count = 0;// provided to count the # of processes // sleeping in the next semaphore // equal to the # or processes executing monitor // operations - 1 Each external function F will be replaced by: wait(mutex); // before entering the monitor … body of F (Monitor Operation); if (next_count > 0) // if there is a process suspended on next signal(next) else signal(mutex); // after leaving the monitor, open the monitor Mutual exclusion within a monitor is ensured.
63
Implementation of Condition Variables
For each condition variable x, we have: semaphore x_sem; // semaphore initiallized to 0 int x_count = 0; The operation x.wait() can be implemented as: x_count++; if (next_count > 0) // Before calling wait(x_sem) signal(next); // unlock the monitor for // processes suspended or else // waiting outside signal(mutex); // the monitor wait(x_sem); // wait on the CV queue x_count--; // After resumed, can be // taken out of CV q. The operation x.signal() can be implemented as: if (x_count > 0) { // only if processes on CV next_count++; // about to suspend itself signal(x_sem); // After signaling, wait. wait(next); // signal and wait // causes signaling process // to suspend themselves //(Hoare semantics) next_count--; }
64
Resuming Processes within a Monitor
If several processes queued on condition x, and x.signal() executed, which should be resumed? Process-resumption order: First-come, first-served (FCFS) : frequently, but not adequate in some cases conditional-wait construct of the form: x.wait(c) where c is priority number (an integer expression): used as the priority number of a process stored with the name of the process Process with lowest number (highest priority) is scheduled next
65
A Monitor to Allocate Single Resource
ResourceAllocator monitor controls the allocation of a single resource among competing processes. Requesting process specifies the maximum time it plans to use the resource. monitor ResourceAllocator { boolean busy; condition x; void acquire(int time) { if (busy) x.wait(time); // monitor allocates the resource to the process busy = TRUE; // with the shortest time-allocation request } void release() { busy = FALSE; x.signal(); initialization code() {
66
Synchronization Examples
Windows Linux Solaris Pthreads
67
Windows Synchronization
Windows OS is a multithreaded kernel for real-time applications and multiple processors When kernel accesses a global resource: a single processor system: temporarily masks interrupts for all interrupt handlers to protect access to global resources a multiprocessor system: protects access to global resources using spinlocks (short code segments), non-preempting threads while holding a spinlock Spinlocking-thread will never be preempted Also provides dispatcher objects for thread synchronization outside the kernel, which may implement mutex locks, semaphores, events, and timers. Events An event acts much like a condition variable Timers notify one or more thread when time expired Dispatcher objects in either signaled-state (object available) or non-signaled state (object not available, thread will block)
68
Linux Synchronization
Prior to kernel Version 2.6, disables interrupts (non-preemptive) to implement short critical sections Now (Version 2.6 and later), fully preemptive Atomic simple math operations for the simplest synchronization technique: atomic_t, atomic_set(), atomic_add(), atomic_sub(), atomic_inc(), atomic_read() Linux provides synchronization mechanisms in the kernel: mutex locks semaphores spinlocks reader-writer versions of both For SMP, spinlock is provided On single-processor system, spinlocks are inappropriate for use and are replaced by Enabling kernel preemption, rather than releasing a spinlock (SMP) preempt_enable() Disabling kernel preemption, rather than holding a spinlock (SMP) preempt_disable()
69
Solaris Synchronization
Implements a variety of locks to support multitasking, multithreading (including real-time threads), and multiprocessing Uses adaptive mutex for efficiency when protecting critical data item from short code segments On SMP: adaptive mutex starts as a standard semaphore implemented as a spinlock If the data are locked and in use, two choices: either spin or block If lock held by a thread running on another CPU, then the thread spins If lock held by non-run-state thread, the thread blocks and sleeps waiting for signal of lock being released Uses condition variables and semaphores for longer code segment Uses readers-writers locks when protecting data accessed frequently but in a read-only manner (multiple readers concurrently) and useful for long sections of code Uses turnstiles, a queue structure containing threads blocked on a lock, to order the list of threads waiting to acquire either an adaptive mutex or reader-writer lock Turnstiles are per-lock-holding-thread, not per-object Priority-inheritance per-turnstile gives the running thread the highest of the priorities of the threads in its turnstile
70
Pthreads Synchronization
Pthreads API is OS-independent It provides: mutex locks : fundamental synchronization technique used with Pthreads condition variables Non-portable extensions include: semaphores spinlocks
71
The Deadlock Problem In a multiprogramming environment, a set of blocked processes each holding a resource and waiting to acquire a resource held by another process in the set Example System has 2 disk drives P1 and P2 each hold one disk drive and each needs another one semaphores A and B, initialized to 1 P0 and P1 wait (A); wait(B) wait (B); wait(A) 71
72
Bridge Crossing Example
Traffic only in one direction Each section of a bridge can be viewed as a resource If a deadlock occurs, it can be resolved if one car backs up (preempt resources and rollback) Several cars may have to be backed up if a deadlock occurs Starvation is possible Note – Most OSes do not prevent or deal with deadlocks 72
73
System Model A system consists of a finite number of resources to be distributed among a number of competing processes. The resources are partitioned into several types, each consisting of some number of identical instances Resource types R1, R2, . . ., Rm CPU cycles, memory space, files, I/O devices Each resource type Ri has Wi instances. Each process utilizes a resource in only the following sequence request use release OS manages resources via request/release system calls. Resource request/release that are not managed by the OS: Through wait() and signal() operations on semaphores Through acquire and release of a mutex lock 73
74
Deadlock A system table with resources, info for free or allocated, an allocated process, and a queue of processes waiting for the resource A set of processes is in a deadlocked state when every process in the set is holding resources and waiting for an event that can be caused only by another process in the set. Multithreaded programs: good candidates for deadlock Multiple threads compete for shared resources.
75
Deadlock Characterization
Deadlock can arise if the following four conditions hold simultaneously in a system. Mutual exclusion: only one process at a time can use a resource in a nonsharable mode Hold and wait: a process holding at least one resource is waiting to acquire additional resources held by other processes No preemption: Resources cannot be preempted; a resource can be released only voluntarily by the process holding it, after that process has completed its task Circular wait: there exists a set {P0, P1, …, Pn} of waiting processes such that P0 is waiting for a resource that is held by P1, P1 is waiting for a resource that is held by P2, …, Pn–1 is waiting for a resource that is held by Pn, and Pn is waiting for a resource that is held by P0. 75
76
Simple Resource Deadlock
77
Related: Indefinite postponement
Also called indefinite blocking or starvation Occurs due to biases in a system’s resource scheduling policies Aging Technique that prevents indefinite postponement by increasing process’s priority as it waits for resource
78
Resource-Allocation Graph
A set of vertices V and a set of edges E V is partitioned into two types: P = {P1, P2, …, Pn}, the set consisting of all the processes in the system R = {R1, R2, …, Rm}, the set consisting of all resource types in the system E is directed edge from process to resource type request edge – directed edge Pi Rj assignment edge – directed edge Rj Pi 78
79
Resource-Allocation Graph (Cont.)
A process A resource type with 4 instances Pi requests instance of Rj Pi is holding an instance of Rj Pi request edge – directed edge Pi Rj Rj Pi assignment edge – directed edge Rj Pi Rj 79
80
Example of a Resource Allocation Graph
80
81
Resource Allocation Graph With Deadlocks
81
82
Graph With A Cycle But No Deadlock
82
83
Basic Facts If graph contains no cycles no deadlock
If graph contains a cycle if only one instance per resource type, then deadlock if several instances per resource type, possibility of deadlock 83
84
Methods for Handling Deadlocks
Prevent or avoid deadlocks by ensuring that the system will never enter a deadlock state Allow the system to enter a deadlock state and then recover Ignore the problem and pretend that deadlocks never occur in the system; used by most operating systems, including UNIX 84
85
Dealing with Deadlock Deadlock prevention Deadlock avoidance
Deadlock detection Deadlock recovery Ignore Deadlock
86
Deadlock Prevention Includes a set of methods for ensuring that at least one of the necessary conditions cannot hold. Prevent deadlocks by constraining request operations To prevent Mutual Exclusion Sharable resources such as read-only files do not cause deadlocks But non-sharable resources, such as tape drivers, require exclusive access by a single process or thread To prevent Hold and Wait – must guarantee that whenever a process requests a resource, it does not hold any other resources Require process to request and be allocated all its resources before it begins execution, or allow process to request resources only when the process has none Low resource utilization; starvation possible 86
87
Deadlock Prevention (Cont.)
To prevent No Preemption – If a process that is holding some resources requests another resource that cannot be immediately allocated to it, then all resources currently being held are released Preempted resources are added to the list of resources for which the process is waiting Process will be restarted only when it can regain its old resources, as well as the new ones that it is requesting To prevent Circular Wait – impose a total ordering of all resource types, and require that each process requests resources in an increasing order of enumeration 87
88
Deadlock Avoidance Requires that the system has some additional information concerning which resources a process will request and use during its lifetime Simplest and most useful model requires that each process declare the maximum number of resources of each type that it may need The deadlock-avoidance algorithm dynamically examines the resource-allocation state to ensure that there can never be a circular-wait condition Resource-allocation state is defined by the number of available and allocated resources, and the maximum demands of the processes 88
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.