Lecture 6 Process Synchronization PDF
Document Details
Uploaded by WellBalancedBinary8128
Mansoura University
Dr. Ehab Essa
Tags
Summary
This lecture covers process synchronization concepts in operating systems, including the critical section problem and its solutions. It explores software and hardware approaches to process synchronization, examining mutex locks, semaphores, and classic synchronization problems.
Full Transcript
Operating Systems Process Synchronization Dr. Ehab Essa Computer Science Department, Mansoura University, Egypt [email protected] Process Synchronization Background The Critical-Section Problem Peterson’s Solution Synchroniza...
Operating Systems Process Synchronization Dr. Ehab Essa Computer Science Department, Mansoura University, Egypt [email protected] Process Synchronization Background The Critical-Section Problem Peterson’s Solution Synchronization Hardware Mutex Locks Semaphores Classic Problems of Synchronization 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 classical process-synchronization problems To explore several tools that are used to solve process synchronization problems Background Processes can be classified into: Independent Process: the execution of one process has no impact on the execution of the others. Cooperative Process: One process's execution has an effect on the execution of other processes. Processes can execute concurrently or in parallel. The CPU scheduler is used to rapidly switches between processes to provide concurrent execution. Additionally, multi-core processor allows parallel execution of different processes. Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution of cooperating processes Background Process Synchronization is the task of coordinating the execution of processes in such a way that no two processes can have access to the same shared data and resources. Race Condition Several processes access and manipulate the same data concurrently may lead to incorrect shared data. The outcome of the execution depends on the particular order in which the access takes place. Processes are "racing" to access/change the data. Producer-Consumer problem The Producer-Consumer problem is a classic multi-process synchronization problem. The job of the Producer is to generate the data, put it into the buffer, and again start generating data. While the job of the Consumer is to consume the data from the buffer. The producers and consumers share the same memory buffer that is of fixed-size (bounded buffer). Producer-Consumer 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. Producer while (true) { while (counter == BUFFER_SIZE) ; buffer[in] = next_produced; in = (in + 1) % BUFFER_SIZE; counter++; } Consumer while (true) { while (counter == 0) ; next_consumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; counter--; } Race Condition counter++ could be implemented as register1 = counter register1 = register1 + 1 counter = register1 counter-- could be implemented as register2 = counter register2 = register2 - 1 counter = register2 Consider this execution interleaving with “count = 5” initially: S0: producer execute register1 = counter {register1 = 5} S1: producer execute register1 = register1 + 1 {register1 = 6} S2: consumer execute register2 = counter {register2 = 5} S3: consumer execute register2 = register2 – 1 {register2 = 4} S4: producer execute counter = register1 {counter = 6 } S5: consumer execute counter = register2 {counter = 4} Critical Section Problem Consider system of n processes {p0, p1, … pn-1} Each process has critical section segment of code Process may be changing common variables, updating table, writing file, etc When one process in critical section, no other may be in its critical section Critical section problem is to design protocol to solve this Each process must ask permission to enter critical section in entry section, may follow critical section with exit section, then remainder section Critical Section General structure of process pi is Solution to Critical-Section Problem 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 the selection of the processes that will enter the critical section next cannot be postponed indefinitely 3. Bounded Waiting - A bound must exist 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 Peterson’s Solution It is a classic software-based solution to the critical-section. Software-based solution: the algorithm involves no special support from the operating system or specific hardware instructions to ensure mutual exclusion It may not work on modern hardware, but it shows a few of important concepts. Peterson's solution is limited to two processes to be synchronized. We have two shared variables: Boolean flag[i]: indicates if a process is interested in entering the critical section int turn: indicates whose turn is to enter the critical section. Algorithm for Process Pi , Pj The structure of a process Pi The structure of a process Pj do { do { flag[i] = true; flag[j] = true; turn = j; turn = i; while (flag[j] && turn == j); while (flag[i] && turn == i); critical section critical section flag[i] = false; flag[j] = false; remainder section remainder section } while (true); } while (true); Peterson’s solution is not guaranteed to work on modern computer architectures: processors and/or compilers can reorder read and write operations that have no dependencies. Synchronization Hardware Many systems provide special hardware instructions for critical section code All solutions below based on idea of locking Protecting critical regions via locks Uniprocessors – could disable interrupts Currently running code would execute without preemption Generally too inefficient on multiprocessor systems Modern machines provide special atomic hardware instructions o Atomic = non-interruptible Either test memory word and set value Or swap contents of two memory words Solution to Critical-section Problem Using Locks do { acquire lock critical section release lock remainder section } while (TRUE); test_and_set Instruction Test_and_Set is an atomic hardware instruction that can be used to solve the synchronization problem. In Test_and_Set, we have a shared lock variable which can take one of the two values, 0 or 1. The Test_and_Set takes a shared variable called target that represent the lock status and return its original value and then set the variable to true which means the lock is enabled. boolean test_and_set (boolean *target) { boolean rv = *target; *target = TRUE; return rv: } Solution using test_and_set() Shared boolean variable lock, initialized to FALSE A process checks the lock before proceeding to the critical section. If it's locked, it keeps waiting until it's unlocked; if it's not, it takes the lock and run the critical section. do { while (test_and_set(&lock)) ; lock = false; } while (true); compare_and_swap Instruction CompareAndSwap hardware instruction is also an atomic instruction. It operates on three variables provided in its parameter. The lock variable is set to new value only if the lock value is equal to compare variable (called expected variable). CompareAndSwap always returns the original value of the lock variable. int compare and swap(int *value, int expected, int new_value) { int temp = *value; if (*value == expected) *value = new_value; return temp; } Solution using compare_and_swap Shared Boolean variable lock initialized to 0 Solution: do { while (compare_and_swap(&lock, 0, 1) != 0) ; lock = 0; } while (true); Bounded-waiting Mutual Exclusion with test_and_set do { waiting[i] = true; key = true; while (waiting[i] && key) key = test_and_set(&lock); waiting[i] = false; j = (i + 1) % n; while ((j != i) && !waiting[j]) j = (j + 1) % n; if (j == i) lock = false; else waiting[j] = false; } while (true); 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, boolean variable indicating if lock is available or not Protect a critical section by First acquire() a lock Then release() the lock. Calls to acquire() and release() must be atomic Usually implemented via hardware atomic instructions But this solution requires busy waiting o This lock therefore called a spinlock acquire() and release() The availability of a mutex lock is indicated by the value of a boolean variable called available. acquire() { while (!available) ; available = false; } release() { available = true; } Mutex Locks The busy loop used to block processes during the acquire phase is one disadvantage with the software implementation shown here (as well as the hardware solutions previously presented). The types of locks that used busy loop are referred to as spinlocks, because the CPU just sits and spins while blocking the process. Spinlocks is not ideal for single-CPU as it is wasteful of CPU cycles that other process might be able to use productively. However, multiprocessor machines can employ spinlocks instead of context switches because they may take considerable time. Operating Systems Semaphore Dr. Ehab Essa Computer Science Department, Mansoura University, Egypt [email protected] Semaphore A semaphore S is an integer variable that can only be modified via two atomic operations: wait() and signal(). Can only be accessed via two indivisible (atomic) operations. wait (S) { while (S value--; if (S->value < 0) { add this process to S->list; block(); } } signal(semaphore *S) { S->value++; if (S->value list; wakeup(P); } } Deadlock and Starvation Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes Let S and Q be two semaphores initialized to 1 Starvation – indefinite blocking 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 Classical Problems of Synchronization Classical problems used to test newly-proposed synchronization schemes Bounded-Buffer Problem Readers and Writers Problem Dining-Philosophers Problem Bounded-Buffer Problem n buffers, each can hold one item Semaphore mutex initialized to the value 1 Semaphore full initialized to the value 0 Semaphore empty initialized to the value n Bounded Buffer Problem (Cont.) The structure of the producer process do {...... wait(empty); wait(mutex);...... signal(mutex); signal(full); } while (true); Bounded Buffer Problem (Cont.) The structure of the consumer process do { wait(full); wait(mutex);...... signal(mutex); signal(empty);...... } while (true); Readers-Writers Problem A data set is shared among a number of concurrent processes Readers – only read the data set; they do not perform any updates Writers – can both read and write Problem – allow multiple readers to read at the same time Only one single writer can access the shared data at the same time Several variations of how readers and writers are treated – all involve priorities: The first variation puts readers first. If a reader asks for access to data and a writer isn't currently using it, the reader gets it. This may result in the starving of writers. The second variation gives priority to the writers. As soon as a writer asks to access the data, all other readers are put on hold, and the writer receives immediate access to the data as it becomes available. Readers-Writers Problem The following code is an example of the first readers-writers variation problem, and involves two binary semaphores and a counter: rw_mutex is a semaphore used to block and release the writers. The first reader to access the data will set this lock and the last reader to exit will release it; The remaining readers do not touch rw_mutex. mutex is a semaphore used solely by readers to restrict access to a variable readcount. (mutex initialized to 1.) read_count is an integer variable used by the reader processes, to count the number of readers currently accessing the data. (read_count initialized to 0.) Readers-Writers Problem (Cont.) The structure of a writer process do { wait(rw_mutex);...... signal(rw_mutex); } while (true); Readers-Writers Problem (Cont.) The structure of a reader process do { wait(mutex); read_count++; if (read_count == 1) wait(rw_mutex); signal(mutex);...... wait(mutex); read_count--; if (read_count == 0) signal(rw_mutex); signal(mutex); } while (true); Dining-Philosophers Problem Philosophers spend their lives thinking and eating Don’t interact with their neighbors, occasionally try to pick up 2 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 o Bowl of rice (data set) o Semaphore chopstick initialized to 1 Dining-Philosophers Problem: Algorithm The structure of Philosopher i: do { wait (chopstick[i] ); wait (chopStick[ (i + 1) % 5] ); // eat signal (chopstick[i] ); signal (chopstick[ (i + 1) % 5] ); // think } while (TRUE); What is the problem with this algorithm? Problems with Semaphores Incorrect use of semaphore operations: signal (mutex) …. wait (mutex) wait (mutex) … wait (mutex) Omitting of wait (mutex) or signal (mutex) (or both) Deadlock and starvation