View
240
Download
4
Category
Tags:
Preview:
Citation preview
Process Synchronization
BackgroundThe Critical-Section ProblemSynchronization HardwareSemaphoresClassical Problems of SynchronizationCritical RegionsMonitors
Concurrent access to shared data may result in data inconsistency.
Maintaining data consistency requires mechanisms to ensure the orderly execution of cooperating processes.
Background
Shared-memory solution to bounded-buffer problem allows at most n – 1 items in buffer at the same time.
A solution, where all N buffers are used is not simple.
◦ Suppose that we modify the producer-consumer code by adding a variable counter, initialized to 0 and incremented each time a new item is added to the buffer
Background
Shared data#define BUFFER_SIZE 10typedef struct {. . .
} item;
item buffer[BUFFER_SIZE];int in = 0;int out = 0;
Solution is correct, but can only use BUFFER_SIZE-1 elements
Bounded-Buffer (Old)
item nextProduced;
while (1) {
while (((in + 1) % BUFFER_SIZE) == out);/* do nothing */
buffer[in] = nextProduced;
in = (in + 1) % BUFFER_SIZE;
}
Bounded-Buffer (Old)– Producer Process
item nextConsumed;
while (1) {
while (in == out); /* do nothing */
nextConsumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
}
Bounded-Buffer (Old)– Consumer Process
Shared data
#define BUFFER_SIZE 10typedef struct {. . .
} item;
item buffer[BUFFER_SIZE];
int in = 0;int out = 0;int counter = 0;
Bounded-Buffer (Proper)
Producer process item nextProduced;while (1) {
while (counter == BUFFER_SIZE); //spin
buffer[in] = nextProduced;in = (in + 1) % BUFFER_SIZE;counter++;
}
Bounded-Buffer
Consumer process
item nextConsumed;while (1) {
while (counter == 0); // do nothing nextConsumed = buffer[out];out = (out + 1) % BUFFER_SIZE;counter--;
}
Bounded-Buffer
The statement “count++ ” may be implemented in machine language as:
register1 = counterregister1 = register1 + 1counter = register1
The statement “count-- ” may be implemented as:register2 = counterregister2 = register2 – 1counter = register2
Bounded Buffer
If both the producer and consumer attempt to update the buffer concurrently, the assembly language statements may get interleaved.
Interleaving depends upon how the producer and consumer processes are scheduled.
Bounded Buffer
Assume counter is initially 5. One interleaving of statements is:
producer: register1 = counterproducer: register1 = register1 + 1 consumer: register2 = counterconsumer: register2 = register2 – 1 consumer: counter = register2 producer: counter = register1
The correct result should be 5.
Race Condition
Race condition: The situation where several processes access – and manipulate shared data concurrently. The final value of the shared data depends upon which process finishes last.
To prevent race conditions, concurrent processes must be synchronized.
Race Condition
Uniprocessor:◦ Disable interrupts to solve lock race condition.
Multiprocessor:◦ Disabling interrupts:◦ Send interrupt message to all processors.◦ Delay in message passing.◦ System degradation.
Solution?◦ Handle lock variable with atomic assignment.◦ Swap variable in hardware.
Disable Interrupts ?
The statements:counter++;counter--;must be performed atomically.
Atomic operation means an operation that completes in its entirety without interruption.
Bounded Buffer
n processes, all competing for some shared data
Each process has a code segment, called critical section, in which the shared data is accessed.
Problem: ◦ ensure that when one process is executing in its
critical section, no other process is allowed to execute in its critical section.
The Critical-Section Problem
General structure of process Pi (other process Pj)
do {entry section
critical sectionexit section
remainder section} while (1);
Processes may share some common variables to synchronize their actions.
General Structure
There are 3 requirements that must be met for a solution to the critical-section problem:
Mutual Exclusion Progress Bounded Waiting
Critical-Section Solution
Mutual Exclusion
If process Pi is executing in its critical section no other processes can be executing in their critical sections.
Critical-Section Solution
Progress
a) if no process is executing in its critical section and b) there exist some processes that wish to enter their
critical section
only those processes that are not executing in the remainder section can participate in the decision on which will enter the critical section next.
The selection of the processes cannot be postponed indefinitely.
Critical-Section Solution
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 non-zero
speed (and completes at a non-infinite time)◦ No assumption concerning relative speed of the n
processes.
Critical-Section Solution
Shared variables: ◦ int turn; // initially turn = 0◦ turn = i Pi can enter its critical section
Process Pido {
while (turn != i); /* NOP */critical section
turn = j;remainder section
} while (1);
Algorithm 1 (2 processes)
Satisfies mutual exclusion (strict alternation), but not progress e.g., When P0 exits CS, sets turn to P1, and executes its remainder section for quite some time, P1 executes CS, sets turn to P0 and P1 immediately wants to executes CS again but has to wait for its turn again. ◦ Remember that P0 is still in the remainder section and is
preventing P1’s entry into the CS because it is P0 turn…
i.e., if turn = 0 and P1 is ready to enter CS, it can’t. Recall Progress requirement:
◦ Only processes not in remainder section can decide on who goes next.
P1 can’t enter even though P0 may be in its remainder section.
Algorithm 1
Problem with Algorithm 1: does not have info on state of processes, only remembers who is next.
Shared variablesboolean flag[2]; // remember state of process flag [0] = flag [1] = false;flag [i] = true;
// Pi ready to enter its critical sectiondo { flag[i] = true; // I am ready to enter CS
while (flag[j]); /* NOP */ critical section flag [i] = false; remainder section} while (1);
Algorithm 2
Satisfies mutual exclusion, but (still) not the progress requirement.
Counterexample:
if flag[0] = true at time t0 then, P0 gets interrupted and P1 gets CPU & sets flag[1] = true at time t1 …
… they both spin lock indefinitely.
Algorithm 2
Combined shared variables of algorithms 1 and 2.Process Pi
do {flag [i] = true;turn = j;while (flag [j] and turn = j);
critical sectionflag [i] = false;
remainder section} while (1);
Meets all three requirements; solves the critical-section problem for two processes.
Algorithm 3 – Peterson’s Solution
Before entering its critical section, process receives a number. Holder of the smallest number enters the critical section.
If processes Pi and Pj receive the same number, check process/thread numbers – if i < j, then Pi is served first; else Pj is served first.
The numbering scheme always generates numbers in increasing order of enumeration; i.e., 1,2,3,3,3,3,4,5...
Bakery Algorithm(Critical section for n processes)
Notation < lexicographical order (ticket #, process id #)◦ (a,b) < (c,d) if
a < c or a = c and b < d (tie broken with smaller pid)
◦ max (a0,…, an-1) is a number, k, such that
k ai for i - 0, …, n – 1
Bakery Algorithm
Shared data:◦ boolean choosing[n]; ◦ int number[n];
Data structures are initialized to false and 0 respectively
Bakery Algorithm
do { choosing[i] = true;
/* Pi will be choosing (assigned) a number */num[i] = max(num[0], num[1], …, num [n – 1])+1;choosing[i] = false;for (j = 0; j < n; j++) {
while (choosing[j]); while ((num[j]!=0)&&(num[j,j]<num[i,i]));
}critical section
num[i] = 0;remainder section
} while (1);
Bakery Algorithm
Simple in uniprocessor environment, i.e., forbid interrupts while a shared variable is modified.◦ Generally too inefficient on multiprocessor
systems Operating systems using this not broadly scalable
Multiprocessor environment: ◦ Interrupt disable is infeasible, i.e., time
consuming since message is passed to other processors.
Synchronization Hardware
Modern machines provide special atomic hardware instructions◦ Atomic = non-interruptable
These can either:◦ test memory word and set value, or◦ swap contents of two memory words
They must accomplish this as one uninterruptable operation
Synchronization Hardware
Test and modify the content of a word atomically
boolean TestAndSet(boolean *target) {boolean rv = *target;*target = true; /* blocks others until lock is set to
false upon CS exit */return rv;
}
Note: If two TestandSet() instructions are executed
simultaneously (on separate processors), they will be executed sequentially in some arbitrary order.
Test and Set
Shared data: boolean lock = false;
Process Pido {
while (TestAndSet(&lock)); /*NOP*/
critical sectionlock = false;
remainder section}
Mutual Exclusion with Test-and-Set
Atomically swap two variables.
void swap(boolean *a, boolean *b) {boolean temp = *a;*a = *b;*b = temp;
}
Hardware Swap
Process Pi
do {key = true;while (key == true)
swap(lock,key);
critical sectionlock = false;
remainder section} while (true);
Mutual Exclusion with Swap
Synchronization tool that does not require busy waiting.
Semaphore S – integer variable◦ can only be accessed via two indivisible (atomic)
operations wait (S):
while S 0 do NOP;S--;
signal (S): S++;
Semaphores
Counting semaphore ◦ integer value can range over an unrestricted domain
Binary semaphore ◦ integer value can range only between 0 and 1; can be
simpler to implement. Also known as mutex locks Can implement a counting semaphore S as a
binary semaphore Provides mutual exclusion
◦ semaphore S; // initialized to 1◦ wait (S);◦ Critical Section◦ signal (S);
Semaphores
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.◦ Could now have busy waiting in critical section implementation
But implementation code is short Little busy waiting if critical section rarely occupied
Note that applications may spend lots of time in critical sections and therefore this is not a good solution.
Semaphores
Semaphores – w/o Busy Waiting
• Associate a waiting queue with each semaphore. • Each entry in a waiting queue has two data items:
value (of type integer) pointer to next record in the list
Two operations: block – place the process invoking the operation on the appropriate
waiting queue.
wakeup – remove one of processes in the waiting queue and place it in the ready queue.
Define a semaphore as a recordtypedef struct { int value; struct process *L;} semaphore;
Assume two simple operations:◦ block suspends the process that invokes it.◦ wakeup(P) resumes the execution of a blocked
process P.
Semaphore Implementation
Semaphore operations now defined as
wait(S):S.value--;if (S.value < 0) {
add this process to S.L;block();
}
Implementation
Semaphore operations now defined as
signal(S): S.value++;
if (S.value <= 0) {remove a process P from S.Lwakeup(P);
}
Implementation
Shared data:semaphore mutex; //initially mutex = 1
Process Pi:
do { wait(mutex); critical section
signal(mutex); remainder section} while (1);
Critical Section of n Processes
Execute B in Pj only after A executed in Pi Use semaphore flag initialized to 0
Code:Pi Pj A wait(flag)signal(flag) B
Semaphore as a General Synchronization Tool
Starvation – indefinite blocking. A process may never be removed from the semaphore queue in which it is suspended.
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 1P0 P1wait(S); wait(Q);wait(Q); wait(S); signal(S); signal(Q);signal(Q) signal(S);
Deadlock and Starvation
Classical Problems of Synchronization
Bounded-Buffer Problem
Readers and Writers Problem
Dining-Philosophers Problem
Shared data
semaphore full, empty, mutex;
Initially:
full = 0, empty = n, mutex = 1
Bounded-Buffer Problem
do { …
produce an item in nextp …
wait(empty);wait(mutex);
…add nextp to buffer
…signal(mutex);signal(full);
} while (1);
Bounded-Buffer Producer Process
do { wait(full)wait(mutex);
…remove item from buffer to nextc
…signal(mutex);signal(empty);
…consume the item in nextc
…} while (1);
Bounded-Buffer Consumer Process
Often, processes will want to access some shared data set (perhaps in a database)
The processes need to control how concurrent readers and writers interact◦ Multiple readers OK◦ Writes must have exclusive access◦ Priority between the two must be decided
Readers-first? Writers-first?
Readers-Writers Problem
Shared data
semaphore mutex, wrt;
Initially
mutex = 1, wrt = 1, readcount = 0;
Readers-Writers Solution
wait(mutex);readcount++;if (readcount == 1)
wait(wrt);signal(mutex);
…reading is performed
…wait(mutex);readcount--;if (readcount == 0)
signal(wrt);signal(mutex):
Readers-Writers: Reader Process
Consider:◦ 5 philosophers sitting around a circular table◦ They spend their lives doing two things:
Thinking Eating
◦ In the center of the table is a bowl of rice◦ Between each pair of philosophers is a single
chopstick◦ When going from thinking to eating, philosophers
attempt to pick up the two adjacent chopsticks (one at a time) and eat.
Dining-Philosophers Problem
Philosopher i:do {wait(chopstick[i])wait(chopstick[(i+1) % 5]) …eat …signal(chopstick[i]);signal(chopstick[(i+1) % 5]); …think …} while (1);
Dining-Philosophers Problem
Is this a good solution?
People make mistakes.
signal(mutex);…Critical section…
wait(mutex);
When would you discover this?
The Big Problem with Semaphores
High-level synchronization construct that allows the safe sharing of an abstract data type among concurrent processes.
monitor monitor-name{
shared variable declarationsprocedure body P1 (…) {
. . .}// more procedure defns here……{
initialization code}
}
Monitors
To allow a process to wait within the monitor, a condition variable must be declared, as
condition x, y;
Condition variable can only be used with the operations wait and signal.
Monitors
x.wait()means that the process invoking this operation is suspended until another process invokes x.signal();
x.signal( ) resumes exactly one suspended process. If no process is suspended, then the signal operation has no effect.
Monitors
monitor dp {enum {THINK, HUNGRY, EAT} state[5];condition self[5];void pickup(int i) // following
slidesvoid putdown(int i) // following
slidesvoid test(int i) // following
slidesvoid init() {
for (int i = 0; i < 5; i++)state[i] = think;
}}
Dining Philosophers Example
void pickup(int i) {state[i] = HUNGRY;test[i];if (state[i] != EAT)
self[i].wait();}
Dining Philosophers
void putdown(int i) {state[i] = THINK;// test left and right neighborstest((i+4) % 5);test((i+1) % 5);
}
Dining Philosophers
void test(int i) {if ( (state[(i + 4) % 5] != EAT) &&
(state[i] == HUNGRY) && (state[(i + 1) % 5] != EAT)) {
state[i] = EAT;self[i].signal();
}}
Dining Philosophers
Variables semaphore mutex; //(initially = 1)semaphore next; // (initially = 0)int next_count = 0;
Monitor Implementation Using Semaphores
Each external procedure F will be replaced by:wait(mutex); … body of F; …if (next_count > 0)
signal(next)else
signal(mutex);
Mutual exclusion within a monitor is ensured.
Monitor Implementation Using Semaphores
For each condition variable x:semaphore x_sem; // (initially = 0)int x_count = 0;
x.wait can be implemented as:x_count++;if (next_count > 0)signal(next);elsesignal(mutex);wait(x_sem);x_count--;
Monitor Implementation
x.signal can be implemented as:
if (x_count > 0) {next_count++;signal(x_sem);wait(next);next_count--;
}
Monitor Implementation
Conditional-wait construct: x.wait(c);
◦ c : integer expression evaluated when the wait operation is executed.
◦ value of c (a priority number) stored with the name of the process that is suspended.
◦ when x.signal is executed: process with smallest associated priority number is
resumed next.
Monitor Implementation
Check two conditions to establish correctness of system:
◦ User processes must always make their calls on the monitor in a correct sequence.
◦ Must ensure that an uncooperative process does not ignore the mutual-exclusion gateway provided by the monitor, and try to access the shared resource directly, without using the access protocols.
Monitor Implementation
Implements a variety of locks to support multitasking, multithreading (including real-time threads), and multiprocessing.
Uses adaptive mutexes for efficiency when protecting data from short code segments.
Uses condition variables and readers-writers locks when longer sections of code need access to data.
Uses turnstiles to order the list of threads waiting to acquire either an adaptive mutex or reader-writer lock.
Solaris 2 Synchronization
Uses interrupt masks to protect access to global resources on uniprocessor systems.
Uses spinlocks on multiprocessor systems.
Also provides dispatcher objects which may act as either mutexes or semaphores.
Dispatcher objects may also provide events. An event acts much like a condition variable.
Windows XP Synchronization
Linux Synchronization Linux:
◦ Prior to kernel Version 2.6, disables interrupts to implement short critical sections
◦ Version 2.6 and later, fully preemptive
Linux provides:◦ semaphores◦ spin locks
Pthreads Synchronization
Pthreads API is OS-independent It provides:
◦ mutex locks◦ condition variables
Non-portable extensions include:◦ read-write locks◦ spin locks
Atomic TransactionsSystem ModelLog-based RecoveryCheckpointsConcurrent Atomic TransactionsTransactional Memory
Assures that operations happen as a single logical unit of work, in its entirety, or not at all
Challenge is assuring atomicity despite computer system failures
Transaction - collection of instructions or operations that performs single logical function◦ Here we are concerned with changes to stable storage – disk◦ Transaction is series of read and write operations, terminated
by commit (transaction successful) or abort (transaction failed) operation
◦ Aborted transaction must be rolled back to undo any changes it performed
Atomic Transaction Model
Types of Storage Media Volatile storage – information stored here does not
survive system crashes◦ Example: main memory, cache
Nonvolatile storage – Information usually survives crashes◦ Example: disk and tape
Stable storage – Information never lost◦ Not actually possible, so approximated via replication or
RAID to devices with independent failure modes
Goal is to assure transaction atomicity where failures cause loss of information on volatile storage
Log-Based Recovery Record to stable storage information about all
modifications by a transaction Most common is write-ahead logging
◦ Log on stable storage, each log record describes single transaction write operation, including Transaction name Data item name Old value New value
◦ <Ti starts> written to log when transaction Ti starts
◦ <Ti commits> written when Ti commits Log entry must reach stable storage before operation on data
occurs
Log-Based Recovery Algorithm Using the log, system can handle any volatile
memory errors◦ Undo(Ti) restores value of all data updated by Ti
◦ Redo(Ti) sets values of all data in transaction Ti to new values
Undo(Ti) and redo(Ti) must be idempotent◦ Multiple executions must have the same result as one
execution If system fails, restore state of all updated data via
log◦ If log contains <Ti starts> without <Ti commits>, undo(Ti)
◦ If log contains <Ti starts> and <Ti commits>, redo(Ti)
Checkpoints Log could become long, and recovery could take a
long time! Checkpoints shorten log and recovery time. Checkpoint scheme:
1. Output all log records currently in volatile storage to stable storage
2. Output all modified data from volatile to stable storage3. Output a log record <checkpoint> to the log on stable
storage Now recovery only includes Ti, such that Ti started
executing before the most recent checkpoint, and all transactions after Ti All other transactions already on stable storage
Concurrent Transactions Must be equivalent to serial execution –
serializability
Could perform all transactions in critical section◦ Inefficient, too restrictive
Concurrency-control algorithms provide serializability
Serializability Consider two data items A and B Consider Transactions T0 and T1
Execute T0, T1 atomically Execution sequence called schedule Atomically executed transaction order
called serial schedule For N transactions, there are N! valid serial
schedules
Nonserial Schedule Nonserial schedule allows overlapped execute
◦ Resulting execution not necessarily incorrect Consider schedule S, operations Oi, Oj
◦ Conflict if access same data item, with at least one write
If Oi, Oj consecutive and operations of different transactions & Oi and Oj don’t conflict◦ Then S’ with swapped order Oj Oi equivalent to S
If S can become S’ via swapping nonconflicting operations◦ S is conflict serializable
Ensure serializability by associating lock with each data item◦ Follow locking protocol for access control
Locks◦ Shared – Ti has shared-mode lock (S) on item Q, Ti can
read Q but not write Q◦ Exclusive – Ti has exclusive-mode lock (X) on Q, Ti can
read and write Q Require every transaction on item Q acquire
appropriate lock If lock already held, new request may have to wait
◦ Similar to readers-writers algorithm
Locking Protocol
Two-phase Locking Protocol Generally ensures conflict serializability
Each transaction issues lock and unlock requests in two phases◦ Growing – obtaining locks◦ Shrinking – releasing locks
Does not prevent deadlock
Managing locks/semaphores/mutex/etc. imposes additional complexity for programmers.◦ Harder to debug or tune for performanceupdate () { acquire(); /* modify shared stuff */ release();
} Much like other high-level constructs, it would
be nice if the system/hardware did this for us.
Transactional Memory
Transactional Memory Systems allow sequences of read/write statements to be declared atomic – and treated like transactionsupdate () { atomic { /* modify shared stuff */ }
} These systems (which can be implemented
either in software or hardware) do the synchronization and optimization for us.
Transactional Memory
Recommended