1 / 32

Process Synchronization

Process Synchronization. Operating System Concepts chapter 6. CS 355 Operating Systems Dr. Matthew Wright. Background. Concurrent access to shared data may result in data inconsistency

erek
Download Presentation

Process Synchronization

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Process Synchronization Operating System Concepts chapter 6 CS 355 Operating Systems Dr. Matthew Wright

  2. Background • Concurrent access to shared data may result in data inconsistency • Maintaining data consistency requires mechanisms to ensure the orderly execution of cooperating processes • Suppose that we wanted to provide a solution to the consumer-producer problem that fills allthe buffers. We can do so by having an integer count that keeps track of the number of full buffers. Initially, count 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 code */ while (count = BUFFER_SIZE) ;// do nothing //add an item to the buffer buffer[in] = item; in = (in + 1) % BUFFER_SIZE; count++; /* consumer code */ while (count = 0) ;// do nothing //add an item to the buffer item = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--;

  3. Implementation • count++ could be implemented as: register1 = count register1 = register1 + 1 count = register 1 • count-- could be implemented as: register2 = count register2 = register2 – 1 count = register 2 • Consider this executing interleaving with count = 5 initially: 1. producer executesregister1 = count(register1 = 5) 2. producer executes register1 = register1 + 1 (register1 = 6) 3. consumer executes register2 = count(register2 = 5) 4. consumer executes register2 = register2 – 1 (register2 = 4) 5. producer executes count = register1(count = 6) 6. consumer executes count = register2(count = 4) • This creates a race condition.

  4. Critical Section • Suppose we have n processes: P0, P1, …, Pn • Each process has a segment of code, called a critical section, in which the process changes shared memory • If one process is executing in its critical section, no other process ought to execute in its critical section • Critical-Section Problem: design a protocol to facilitate process communication and protect shared resources • Standard design: • Entry section: a process must request permission to enter critical section • Process executes critical section • Exit section: process notifies others that it has exited its critical section • Remainder section: any code following the critical section /* typical process structure */ while (true) { entry section critical section exit section remainder section }

  5. Requirements A solution to the critical-section problem must satisfy: • Mutual Exclusion:If process Pi is executing in its critical section, then no other processes can be executing in their critical sections. • 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. • 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. • Assume that each process executes at a nonzero speed • No assumption concerning relative speed of the processes

  6. Peterson’s Solution • Works for two processes, denoted P0 and P1 • Requires that machine-language load and store operations are atomic—that is, they cannot be interrupted • The two processes share two data items: int turn; boolean flag[2]; • Variable turn indicates whose turn it is to enter the critical section. • Array flag indicates whether each process is ready to enter its critical section. If flag[i] = true, then Pi is ready.

  7. Peterson’s Solution Examine the code to see that this satisfies the three requirements: • Mutual exclusion • Progress • Bounded Waiting while (true) { flag[i] = true; turn = j; while (flag[j] && turn == j) /* wait */ ; critical section flag[i] = false; remainder section }

  8. Locks • We can prevent race conditions by protecting critical section with locks. • A process must acquire a lock before entering its critical section. • A process releases a lock when it exits its critical section. • Locks may be implemented in software or hardware. /* solution using locks */ while (true) { acquire lock critical section release lock remainder section }

  9. Hardware Solutions • Many systems provide hardware support for critical section code. • On a single-processor system, we could disable interrupts when a process executes its critical section. • Currently running code would execute without preemption • Generally too inefficient on multiprocessor systems • Operating systems using this not broadly scalable • Modern machines provide special atomic(non-interruptible) hardware instructions. For example: • Test memory word and set value • Swap contents of two memory words • Java HardwareData class presents an abstract view of atomic hardware instructions

  10. Java HardwareData /** * Sample data structure for hardware solution * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.4 */ public classHardwareData { privatebooleanvalue = false; publicHardwareData(boolean value) { this.value = value; } public booleanget() { return value; } public void set(booleannewValue) { this.value = newValue; } public booleangetAndSet(booleannewValue) { booleanoldValue = this.get(); this.set(newValue); returnoldValue; } public void swap(HardwareData other) { booleantemp = this.get(); this.set(other.get()); other.set(temp); } } /** * Using HardwareDatagetAndSet() to implement a lock * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.5 */ //lock is shared by all threads HardwareData lock = new HardwareData(false); while (true) { while (lock.getAndSet(true)) Thread.yield(); /* critical section */ lock.set(false); /* remainder section */ } /** * Using HardwareData swap() to implement a lock * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.6 */ //lock is shared by all threads HardwareData lock = new HardwareData(false); //each thread has a local copy of key HardwareDatakey = new HardwareData(true); while (true) { key.set (true); do { lock.swap(key); } while (key.get() == true); /* critical section */ lock.set(false); /* remainder section */ } Each of these methods must be implemented atomically.

  11. Semaphores /* definitions of acquire() and release() */ acquire() { while value <= 0 ; // wait value--; } release() { value++; } • Simpler synchronization tool than the previous • A semaphore consists of: • an integer variable • atomic operations acquire() and release() • The value of a counting semaphore can be any integer. • The value of a binary semaphore (also called a mutex lock) is only 0 or 1. • A binary semaphore can control access to a critical section. /* using a binary semaphore to control access to a critical section */ Semaphore sem = new Semaphore(1); sem.acquire(); /* critical section */ sem.release(); /* remainder section */

  12. Multiple Java Threads using Semaphores /** * Worker thread to demonstrate use of a semaphore * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.8 */ public class Worker implements Runnable { private Semaphore sem; public Worker(Semaphore sem) { this.sem = sem; } public void run() { while (true) { sem.acquire(); criticalSection(); sem.release(); remainderSection(); } } } /** * create a semaphore and five threads * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.8 */ public classSemaphoreFactory { public static void main(Stringargs[]) { Semaphore sem = new Semaphore(1); Thread[] bees = new Thread[5]; for (int i = 0; i < 5; i++) bees[i] = new Thread(newWorker(sem)); for (int i = 0; i < 5; i++) bees[i].start(); } }

  13. Semaphore Implementation /* definitions of acquire() and release() */ acquire() { value--; if (value <= 0) { add this process to list block(); } } release() { value++; if (value < 0) { remove a process P from list wakeup(P); } } • Disadvantage of previous implementation: busy waiting • Better idea: • When a process fails to acquire a semaphore, it places itself in a waiting queue associated with the semaphore. • When a process releases a semaphore, it restarts a process waiting for the semaphore. • This requires a semaphore to be an integer value and a list of processes. • Now value might be negative; if so, its magnitude is the number of waiting processes.

  14. Semaphore Implementation /* definitions of acquire() and release() */ acquire() { value--; if (value <= 0) { add this process to list block(); } } release() { value++; if (value < 0) { remove a process P from list wakeup(P); } } • Operating provides block() and wakeup(P) as basic system calls • The list may be implemented as any type of queue. • Important: acquire() and release() must be implemented atomically—this creates a critical section problem! • We have transferred the critical section problem from the application program to the Semaphore class, but it’s progress, since the new critical sections are short. • Possible solutions: • Disable interrupts during acquire() and release() • Use a hardware solution

  15. Possible Problem: Deadlock • A semaphore with a waiting queue can result in a situation where two processes are waiting for an event that can only be caused by the other waiting process. • This is called a deadlock. • Example: P0P1 S.acquire(); Q.acquire(); Q.acquire(); S.acquire(); S.release();Q.release(); Q.release();S.release(); • Deadlocks are the subject of Chapter 7. • A related problem is indefinite blocking or starvation, in which a process waits indefinitely to acquire the semaphore.

  16. Possible Problem: Priority Inversion • A high-priority process might have to wait for a low-priority process to release a shared resource. • Example: • Three processes, L, M, and H, with priorities L < M < H • Process H is waiting for resource R, which is currently locked by process L • Process M becomes runnable and preempts process L • Now process M has caused process H to wait longer • This problem is called priority inversion. • Possible solutions: • Have only two priorities • Priority-inheritance protocol: all processes accessing resources needed by higher-priority processes inherit the higher priority until they release the resources

  17. Bounded Buffer Problem • Recall: BoundedBuffer helps solve the Producer-Consumer Problem • We can use semaphores to protect the shared memory items in the BoundedBuffer object /* producers call this method */ public void insert(E item) { empty.acquire(); mutex.acquire(); // add an item to the buffer buffer[in] = item; in = (in + 1) % BUFFER_SIZE; mutex.release(); full.release(); } /* consumers call this method */ public void remove(E item) { E item; full.acquire(); mutex.acquire(); // remove an item to the buffer item = buffer[out]; out = (out + 1) % BUFFER_SIZE; mutex.release(); full.release(); return item; } /** * bounded buffer with semaphores * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.9 */ import java.util.*; public class BoundedBuffer<E> implements Buffer<E> { private static finalint BUFFER_SIZE = 5; private E[] buffer; privateint in, out; private Semaphore mutex; private Semaphore empty; private Semaphore full; public BoundedBuffer() { // buffer is initially empty in = 0; out = 0; mutex = new Semaphore(1); empty = new Semaphore(BUFFER_SIZE); full = new Semaphore(0); buffer = (E[]) new Object[BUFFER_SIZE]; } public void insert(E item) { ... } public void insert(E item) { ... } }

  18. Readers and Writers Problem • Suppose several concurrent processes share access to a database. • Some processes only read the database (call them readers), others read and write (call them writers). • Readers-writers problem: We must ensure that writers have exclusive access to the shared database. • Two variations: • First readers-writers problem: no reader should be kept waiting unless a writer has already obtained permission to use the database (writers might starve) • Second readers-writers problem: once a writer is ready, it must be able to write as soon as possible (readers might starve) • The will examine a Java solution using semaphores.

  19. Readers and Writers Problem /** * Database class, with methods to coordinate reader and writer * access to the database * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.18 */ public class Database implements ReadWriteLock { private intreaderCount;// number of active readers Semaphore mutex; // controls access to readerCount Semaphore db; // controls access to the database public Database() { readerCount = 0; mutex = new Semaphore(1); db = new Semaphore(1); } public void acquireReadLock(intreaderNum) { mutex.acquire(); /* first reader indicates that the database is being read */ ++readerCount; if(readerCount == 1) db.acquire(); mutex.release(); } public void releaseReadLock(intreaderNum) { mutex.acquire(); /* last reader indicates database is no longer being read */ --readerCount; if(readerCount == 0) db.release(); mutex.release(); } public void acquireWriteLock(intwriterNum) { db.acquire(); } public void releaseWriteLock(intwriterNum) { db.release(); } }

  20. Dining Philosophers Problem • Five philosophers sit around a circular table, with a single chopstick between each two philosophers. • Philosophers alternate between thinking and eating. • When a philosopher wants to eat, he first must pick up the two neighboring chopsticks, one after another. • When a philosopher finishes eating, he returns the two chopsticks to the table. • Problem: How can we prevent deadlock and starvation?

  21. Dining Philosophers Problem /* shared data (chopsticks) */ Semaphore chopStick[] = new Semaphore[5]; for(int i=0; i < 5; i++) chopStick[i] = new Semaphore(1); • Simple solution: represent each chopstick by a semaphore. • This does not solve the possibility of deadlock. /* the structure of philosopher i */ while (true) { // get left chopstick chopStick[i].acquire(); // get right chopstick chopStick[(i+1)%5].acquire(); eating(); // return left chopstick chopStick[i].release(); // return right chopstick chopStick[(i+1)%5].release(); thinking(); } • Possible solutions: • Allow at most four philosophers at the table • Allow a philosopher to pick up chopsticks only if both are available (must pick them up in a critical section) • Require odd philosophers to pick up left chopstick first, and even philosophers to pick up right chopstick first.

  22. Problems with Semaphores If a single process is not well-behaved, then synchronization may fail. For example: semaphore.acquire(); critical section semaphore.release(); semaphore.acquire(); critical section semaphore.acquire(); produces deadlock Correct. semaphore.release(); critical section semaphore.acquire(); //semaphore.acquire(); critical section semaphore.release(); violates mutual-exclusion requirement (error might not always occur) violates mutual-exclusion requirement

  23. Monitors monitor monitor name { //shared variable declarations ... //methods initialization code (...) { ... } procedure P1 (...) { ... } procedure P2 (...) { ... } ... } • A monitoris an abstract type that controls access to a shared resource. • The shared resource is only available via methods provided by the monitor. • The monitor ensures that only one process at a time can access the shared resource.

  24. Java Synchronization • Java allows methods to be declared synchronized. • Each Java object has an associated lock, which we have ignored until now, and which may be owned by a single thread. • A thread acquires the lock for an object by calling a synchronized method of that object. • If no other thread owns the lock, then the calling thread gains ownership of the lock and runs the synchronized method. • If some thread owns the lock, then the calling thread waits in the entry set for the lock. • When a thread exists a synchronized method, it releases the lock.

  25. Synchronized BoundedBuffer /** * Implementing a bounded buffer with Java synchronization * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.27 */ public classBoundedBuffer<E> { private static finalintBUFFER_SIZE = 5; privateintcount; // number of items in the buffer privateintin; // points to the next free position privateintout; // points to the next full position private E[] buffer; // constructor publicBoundedBuffer() { ... } // Producers call this method public synchronized void insert(E item) { while (count == BUFFER_SIZE) Thread.yield(); buffer[in] = item; in = (in + 1) % BUFFER_SIZE; ++count; } // Consumers call this method public synchronized void remove(E item) { E item; while(count == 0) Thread.yield(); item = buffer[out]; out = (out + 1) % BUFFER_SIZE; --count; return item; } } Problem: Deadlock If the Producer tries to insert an item into a full buffer, it will sleep in the insert method and the Consumer will never be able to gain the lock to consume an item.

  26. Java: Wait and Notify • Each Java object also has a wait set. • When a thread enters a synchronized method, it gains the lock for the object. • If the thread determines that it has to wait for something, it can call the wait() method. When this happens: • The thread releases the lock for the object. • The state of the thread is set to blocked. • The thread is placed in the wait set for the object. • The notify() method does the following: • Picks a thread T from the wait set • Moves T from the wait set to the entry set • Changes the state of T from blocked to runnable

  27. Synchronized BoundedBuffer /** * Implementing a bounded buffer with Java synchronization * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.27 */ public classBoundedBuffer<E> { private static finalintBUFFER_SIZE = 5; privateintcount, in, out; private E[] buffer; // constructor publicBoundedBuffer() { ... } // Producers call this method public synchronized void insert(E item) { while (count == BUFFER_SIZE) { try { wait(); } catch (InterruptedException e) {} } buffer[in] = item; in = (in + 1) % BUFFER_SIZE; ++count; notify(); } // Consumers call this method public synchronized void remove(E item) { E item; while(count == 0) try { wait(); } catch (InterruptedException e) {} } item = buffer[out]; out = (out + 1) % BUFFER_SIZE; --count; notify(); return item; } }

  28. Multiple Notification • The notify() method selects an arbitrary thread from the wait set. • It is possible that the selected thread is not, in fact, waiting on the condition for which it is notified. • The notifyAll() method moves all threads from the wait set to the entry set. • notifyAll() is a more expensive operation than notify(), but it is necessary if you must wake one particular thread out of many that might be sleeping

  29. Java Synchronization Example Solving the Readers-Writers problem using Java synchronization… /** * Database class, with methods to coordinate reader and writer * access to the database using Java synchronization * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.33-6.35 */ public class Database implements ReadWriteLock { private intreaderCount;// number of active readers private booleandbWriting; // indicates whether databse is in use public Database() { readerCount = 0; dbWriting= false; } public synchronized void acquireReadLock() { while (dbWriting == true) { try { wait(); } catch (InterruptedException e) { } } ++readerCount; } public synchronizedvoid releaseReadLock() { --readerCount; /* last reader indicates database is no longer being read */ if(readerCount == 0) notify();//wake up a writer } public synchronizedvoid acquireWriteLock() { while (readerCount > 0 || dbWriting == true) { try { wait(); } catch(InterrputedException e) { } } /* once there are no readers or writers, indicate that the database is in use */ dbWriting = true; } public synchronizedvoid releaseWriteLock() { dbWriting = false; notifyAll(); //wake up all readers } }

  30. Java Block Synchronization • The amount of time between when a lock is acquired and when it released is called the scope of the lock. • A synchronized method could be too large of a scope. • Java allows blocks of code to be declared synchronized. • Example: Object lock = new Object(); ... public void someMethod() { nonCriticalSection(); synchronized(lock) { criticalSection(); } remainderSection(); }

  31. Java Synchronization Notes • A thread can nest synchronized method invocations for different objects in order to own multiple locks simulaneously. • wait(), notify(), and notifyAll() may only be called from synchronized methods or blocks. • wait() throws an InterrupedException if the interruption status of its thread is set—that is, if someone has called interrupt() for that thread

  32. Synchronization in Java 5 • ReentrantLock • like the synchronized statement, but with more features • Allows granting the lock to the longest-waiting thread • Counting semaphores • controls access to a resource that may be simultaneously accessed by some fixed number of threads • provides acquire() and release() methods • Condition variables • provide similar functionality to wait() and notify() • offer more flexibility for threads that must wait for specific conditions

More Related