OS Chapter 5 Process Synchronization
Solution to Dining Philosophers
Each philosopher i invokes the operations pickup() and putdown() in the following sequence: 1. DiningPhilosophers.pickup(i); 2. EAT 3. DiningPhilosophers.putdown(i); ◆ No deadlock, but starvation is possible
Critical Section
Each process has critical section segment of code. Process may be changing common variables, updating table. If one process is in critical section, no other may be in its critical section
Solving critical section problem
Each process must ask permission to enter critical section in entry section, may follow critical section with exit section
Locking Protocol
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
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
Progress
If no process is executing in its critical section, some other process wants to enter its critical section, then selection of processes that will enter the critical section next cannot be postponed indefinitely.
Mutual Exclusion
If process Pi is executing in its critical section, then no other processes can be executing in their critical sections
Concurrent Transactions
Must be equivalent to serial execution - serializability ❖ Could perform all transactions in critical section ◆ Inefficient, too restrictive ❖ Concurrency-control algorithms provide serializability
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. ◆ 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.
Solution to Critical Section problem
Mutual Exclusion Progress Bounded Waiting
Peterson's Solution
Two process solution Two processes share two variables; int turn ,boolean flag Variable turn indicates whose turn it is to enter the critical section flag array is used to indicate if a process is ready to enter the critical section
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
Semaphores with no Busy waiting
With each semaphore there is an associated waiting queue: ◆ 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.
acquire() and release()
acquire() { while (!available) ; /* busy wait */ available = false;; } release() { available = true; } do { acquire lock /* critical section */ release lock /* remainder section */ } while (true);
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.
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
Monitors
A high-level abstraction that provides a convenient and effective mechanism for process synchronization ❖ Abstract data type, internal variables only accessible by code within the procedure ❖ Only one process may be active within the monitor at a time ❖ But not powerful enough to model some synchronization schemes
System Model
Assures that operations happen as a single logical unit of work, in its entirety, or not at all ❖ Related to field of database systems ❖ 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
Classical Problems of Synchronization
Bounded-Buffer Problem ◆ Readers and Writers Problem ◆ Dining-Philosophers Problem
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
Semaphore usage
Counting semaphore ◆ integer value can range over an unrestricted domain ❖ Binary semaphore ◆ integer value can range only between 0 and 1 ◆ Essentially a mutex lock.
Timestamp-based Protocol Implementation
Data item Q gets two timestamps ◆ W-timestamp(Q) - largest timestamp of any transaction that executed write(Q) successfully ◆ R-timestamp(Q) - largest timestamp of successful read(Q) ◆ Updated whenever read(Q) or write(Q) executed ❖ Timestamp-ordering protocol assures any conflicting read and write executed in timestamp order ❖ Suppose Ti executes read(Q) ◆ If TS(Ti) < W-timestamp(Q), Ti needs to read value of Q that was already overwritten ■ read operation rejected and Ti rolled back ◆ If TS(Ti) ≥ W-timestamp(Q) ■ read executed, R-timestamp(Q) set to max(R-timestamp(Q), TS(Ti))
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. P0 P1 wait(S); wait(Q); wait(Q); wait(S); . . . . . . signal(S); signal(Q); signal(Q); signal(S);
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
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 ■ Bowl of rice (data set) ■ Semaphore chopstick [5] initialized to 1
Solution to Critical Section Problem-Two approaches depending on kernel
Preemptive Non-Preemptive
Mutex Locks
Product critical regions 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 ◆ But this solution requires busy waiting ■ This lock is therefore called a spinlock.
Pthreads Syncronization
Pthreads API is OS-independent ❖ It provides: ◆ mutex locks ◆ condition variables ❖ Non-portable extensions include: ◆ read-write locks ◆ spinlocks
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
Timestamp-based Protocols
Select order among transactions in advance - timestamp-ordering ❖ Transaction Ti associated with timestamp TS(Ti) before Ti starts ◆ TS(Ti) < TS(Tj) if Ti entered system before Tj ◆ TS can be generated from system clock or as logical counter incremented at each entry of transaction ❖ Timestamps determine serializability order ◆ If TS(Ti) < TS(Tj), system must ensure produced schedule equivalent to serial schedule where Ti appears before Tj
Solution using test and set
Shared boolean variable lock, initialized to FALSE ❖ Solution: do { while ( test_and_set(&lock) ) ; /* do nothing */ /* critical section */ lock = false; // Ponder: Why isn't this atomic? /* remainder section */ } while (true);
Solution using compare and swap
Solution: do { while ( compare_and_swap(&lock, 0, 1) != 0 ) ; /* do nothing - just wait */ /* critical section */ lock = 0; /* remainder section */ } while (true);
Timestamp-ordering Protocol
Suppose Ti executes write(Q) ◆ If TS(Ti) < R-timestamp(Q), value Q produced by Ti was needed previously and Ti assumed it would never be produced ■ Write operation rejected, Ti rolled back ◆ If TS(Ti) < W-timestamp(Q), Ti attempting to write obsolete value of Q ■ Write operation rejected and Ti rolled back ◆ Otherwise, write executed ❖ Any rolled back transaction Ti is assigned new timestamp and restarted ❖ Algorithm ensures conflict serializability and freedom from deadlock
Semaphore
Synchronization tool that does not require busy waiting. ◆ Semaphore S - integer variable ❖ Two standard operations modify S: ◆ wait() and signal() ■ Originally called P() and V() ◆ Less complicated ◆ Can only be accessed via two indivisible (atomic) operations
Atomic Transactions
System Model ❖ Log-based Recovery ❖ Checkpoints ❖ Concurrent Atomic Transactions
Preemptive
allows preemption of process when running in kernel mode
Atomic hardware instructions
atomic- non interruptive either test memory word and set value or swap contents of two memory words
Concurrent access to shared data may result in
data inconsistency
Critical Section code
do { [entry section] critical section [exit section] remainder section }while (true);
CSP using Locks
do { acquire lock // critical section goes here release lock //remainder section goes here }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; /* 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
Starvation
indefinite blocking --a process may never be removed from the semaphore queue in which it is suspended
How to solve the consumer-producer problem?
make a integer counter that keeps track of the number of full buffers. Counter set to 0 initially and incremented by producer after it producers new buffer and is decremented by consumer after it consumes a buffer.
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
Locking
protecting critical regions via locks Synchronization Hardware(hardware support for critical section code)
Non-preemptive
runs until exits kernel mode,blocks,or voluntarily yields CPU -free of race conditions in kernel mode.
Priority Inversion
scheduling problem when lower priority process holds a lock needed by higher priority process
Solution to Dining Philosophers
test (int i) { if ( (state[(i + 4) % 5] != EATING) && (state[i] == HUNGRY) && (state[(i + 1) % 5] != EATING) ) { state[i] = EATING ; self[i].signal () ; } } initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING; } }
Producer
while (true) { /* produce an item in next produced */ while (counter == BUFFER_SIZE) ; /* do nothing */ buffer[in] = next_produced; in = (in + 1) % BUFFER_SIZE; counter++; }
Consumer
while (true) { while (counter == 0) ; /* do nothing */ next_consumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; counter--; /* consume the item in next consumed */ }
Can processes be executed Concurrently?
yes