background concurrent access to shared data may result in data inconsistency maintaining data...

32
Process Synchronizatio n CS 355 Operating Systems Dr. Matthew Wright Operating System Concepts chapter 6

Upload: simon-george

Post on 11-Jan-2016

222 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Process Synchronization

CS 355Operating Systems

Dr. Matthew Wright

Operating System Conceptschapter 6

Page 2: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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 all the 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 bufferbuffer[in] = item;in = (in + 1) % BUFFER_SIZE;count++;

/* consumer code */while (count = 0)

; // do nothing

//add an item to the bufferitem = buffer[out];out = (out + 1) % BUFFER_SIZE;count--;

Page 3: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Implementation• count++ could be implemented as:

register1 = countregister1 = register1 + 1count = register 1

• count-- could be implemented as:register2 = countregister2 = register2 – 1count = register 2

• Consider this executing interleaving with count = 5 initially:1. producer executes register1 = 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.

Page 4: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

/* typical process structure */while (true) {

entry section

critical section

exit section

remainder section}

• Standard design:1. Entry section: a process must request

permission to enter critical section2. Process executes critical section3. Exit section: process notifies others

that it has exited its critical section4. Remainder section: any code

following the critical section

Page 5: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

RequirementsA solution to the critical-section problem must satisfy: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.• Assume that each process executes at a nonzero speed • No assumption concerning relative speed of the processes

Page 6: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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.

Page 7: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Peterson’s Solution

Examine the code to see that this satisfies the three requirements:1. Mutual exclusion2. Progress3. Bounded Waiting

while (true) {flag[i] = true;turn = j;while (flag[j] && turn ==

j)/* wait */ ;

critical section

flag[i] = false;

remainder section

}

Page 8: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

}

Page 9: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

Page 10: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Java HardwareData/** * Sample data structure for hardware solution * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.4*/

public class HardwareData{

private boolean value = false;

public HardwareData(boolean value) {this.value = value;

}

public boolean get() {return value;

}

public void set(boolean newValue) {this.value = newValue;

}

public boolean getAndSet(boolean newValue) {boolean oldValue = this.get();this.set(newValue);

return oldValue;}

public void swap(HardwareData other) {boolean temp = this.get();

this.set(other.get());other.set(temp);

}}

Each of these methods must be

implemented atomically.

/** * Using HardwareData getAndSet() to implement a lock * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.5*/

//lock is shared by all threadsHardwareData 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 threadsHardwareData lock = new HardwareData(false);

//each thread has a local copy of keyHardwareData key = new HardwareData(true);

while (true) {key.set (true);

do {lock.swap(key);

}while (key.get() == true);

/* critical section */

lock.set(false);

/* remainder section */}

Page 11: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Semaphores• 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.

/* definitions of acquire() and release() */

acquire() {while value <= 0

; // waitvalue--;

}

release() {value++;

}/* using a binary semaphore to control access to a critical section */

Semaphore sem = new Semaphore(1);

sem.acquire();

/* critical section */

sem.release();

/* remainder section */

Page 12: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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 class SemaphoreFactory{ public static void main(String args[]) { Semaphore sem = new Semaphore(1); Thread[] bees = new Thread[5];

for (int i = 0; i < 5; i++) bees[i] = new Thread(new Worker(sem));

for (int i = 0; i < 5; i++) bees[i].start(); }}

Page 13: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Semaphore Implementation• 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.

/* 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);}

}

Page 14: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Semaphore Implementation• 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

/* 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);}

}

Page 15: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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:

P0 P1

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.

Page 16: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

Page 17: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

/** * 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 final int BUFFER_SIZE = 5;private E[] buffer;private int in, out;private Semaphore mutex;private Semaphore empty;private Semaphore full;

public BoundedBuffer(){

// buffer is initially emptyin = 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) {...

}}

/* producers call this method */

public void insert(E item) {empty.acquire();mutex.acquire();

// add an item to the bufferbuffer[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 bufferitem = buffer[out];out = (out + 1) % BUFFER_SIZE;

mutex.release();full.release();

return item;}

Page 18: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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.

Page 19: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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 int readerCount; // number of active readersSemaphore mutex; // controls access to readerCountSemaphore db; // controls access to the database

public Database() {readerCount = 0;mutex = new Semaphore(1);db = new Semaphore(1);

}

public void acquireReadLock(int readerNum) {mutex.acquire();

/* first reader indicates that the database is being read */

++readerCount;if (readerCount == 1)

db.acquire();mutex.release();

}

public void releaseReadLock(int readerNum) {mutex.acquire();

/* last reader indicates database is no longer being read */

--readerCount;if (readerCount == 0)

db.release();

mutex.release();}

public void acquireWriteLock(int writerNum) {db.acquire();

}

public void releaseWriteLock(int writerNum) {db.release();

}}

Page 20: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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?

Page 21: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Dining Philosophers Problem• Simple solution: represent

each chopstick by a semaphore.

• This does not solve the possibility of deadlock.

/* shared data (chopsticks) */Semaphore chopStick[] = new Semaphore[5];

for(int i=0; i < 5; i++)chopStick[i] = new Semaphore(1);

/* the structure of philosopher i */while (true) {

// get left chopstickchopStick[i].acquire();// get right chopstick

chopStick[(i+1)%5].acquire();

eating();

// return left chopstickchopStick[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.

Page 22: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Problems with SemaphoresIf a single process is not well-behaved, then synchronization may fail. For example:

semaphore.acquire();

critical section

semaphore.release();

semaphore.release();

critical section

semaphore.acquire();

semaphore.acquire();

critical section

semaphore.acquire();

//semaphore.acquire();

critical section

semaphore.release();

Correct.

violates mutual-exclusion requirement (error might

not always occur)

produces deadlock

violates mutual-exclusion requirement

Page 23: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Monitors• A monitor is 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.

monitor monitor name{

//shared variable declarations

...

//methodsinitialization code (...)

{...

}

procedure P1 (...) {...

}

procedure P2 (...) {...

}

...

}

Page 24: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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.

Page 25: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Synchronized BoundedBuffer/** * Implementing a bounded buffer with Java synchronization * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.27*/

public class BoundedBuffer<E>{

private static final int BUFFER_SIZE = 5;private int count; // number of items in the bufferprivate int in; // points to the next free position private int out; // points to the next full positionprivate E[] buffer;

// constructorpublic BoundedBuffer() {

...}

// Producers call this methodpublic synchronized void insert(E item) {

while (count == BUFFER_SIZE)Thread.yield();

buffer[in] = item;in = (in + 1) % BUFFER_SIZE;++count;

}

// Consumers call this methodpublic synchronized void remove(E item) {

E item;

while (count == 0)Thread.yield();

item = buffer[out];out = (out + 1) % BUFFER_SIZE;--count;

return item;}

}

Problem: DeadlockIf 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.

Page 26: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Java: Wait and Notify• Each Java object also

has a wait set.• When a thread enters asynchronized 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

Page 27: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Synchronized BoundedBuffer/** * Implementing a bounded buffer with Java synchronization * * @author Gagne, Galvin, Silberschatz * Operating System Concepts with Java, 8th Ed., Fig. 6.27*/

public class BoundedBuffer<E>{

private static final int BUFFER_SIZE = 5;private int count, in, out; private E[] buffer;

// constructorpublic BoundedBuffer() {

...}

// Producers call this methodpublic 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 methodpublic 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;}

}

Page 28: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

Page 29: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

Java Synchronization ExampleSolving 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 int readerCount; // number of active readersprivate boolean dbWriting; // 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 synchronized void releaseReadLock() {--readerCount;

/* last reader indicates database is no longer being read */

if (readerCount == 0)notify(); //wake up a writer

}

public synchronized void 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 synchronized void releaseWriteLock() {dbWriting = false;

notifyAll(); //wake up all readers}

}

Page 30: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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();}

Page 31: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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

Page 32: Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution

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