Operating Systems - Process Synchronization (Night Class)
By Aurelie A. Peralta
A cooperating
process is one that can affect or be affected by other processes executing in the
system. Cooperating processes may either
directly share a logical address space,
or be allowed to share data only through files. The former case is achieved through
the use of lightweight processes or threads.
The Critical-Section Problem
Each process
has a segment of code, called a critical section, in which the process may be changing
common variables, updating a table, writing a file, and so on. The important feature
of the system is that, when one process is executing in its critical section, no
other process is to be allowed to execute in its critical section. Thus, the execution
of critical sections by the process is mutually exclusive in time.
A solution
to the critical-section problem must satisfy the following requirements:
1. Mutual
exclusion - no other process can be executing in their critical sections.
2. Progress
3. Bounded
waiting
Semaphores
The solutions
to the critical-section problem are not easy to generalize to more complex problems.
To overcome this difficulty, we can use a synchronization tool called semaphore.
A semaphore S is an integer variable that, apart from initialization, is accessed
only through two standard atomic operations: wait and signal. These operations were originally termed P for wait and V for signal.
Deadlocks and Starvation
The implementation
of a semaphore with a waiting queue may result in a situation where two or more
processes are waiting indefinitely for an event that can be caused only by one of
the waiting processes. The event in question is the execution of a signal operation.
When such a state is reached, these processes are said to be deadlocked.
Another
problem related to deadlocks is indefinite blocking or starvation, a situation where
processes wait indefinitely within the semaphore. Indefinite blocking may occur
if we add and remove processes from the list associated with a semaphore in LIFO
order.
Classic Problems of Synchronization
1. The
Bounded-Buffer Problem
2. The
Readers-Writers Problem
3. The
Dining-Philosophers Problem - consider five philosophers who spend their lives thinking
and eating. The philosophers share a common circular table surrounded by five chairs,
each belonging to one philosopher. In the center of the table is a bowl of rice,
and the table is laid with five single chopsticks. When a philosopher gets hungry,
she does not interact with her colleagues. From time to time, a philosopher gets
hungry and tries to pick up the two chopsticks that are closest to her. A philosopher
may pick up only one chopsticks at a time. Obviously, she cannot pick up a chopstick
that is already in the hand of a neighbor. When a hungry philosopher has both her
chopsticks at
the same time, she eats without releasing her chopsticks. When she
is finished eating, she puts down both of her chopsticks and starts thinking again.
Monitors
Another
high-level synchronization construct is the monitor type. A monitor is characterized
by a set of programmer-defined operators. The representation of a monitor type consists
of declarations of variables whose values define the state of an instance of the
type, as well as the bodies of procedures or functions that implement operations
on the type.
Given a
collection of cooperating sequential processes that share data, mutual exclusion
must be provided. One solution is to ensure that a critical section of code is in
use by only one process or thread at a time. Different algorithms exist for solving
the critical-section problem, with the assumption that only storage interlock is
available.
The main disadvantage of these user-coded solutions is that they all require busy waiting.
Semaphores overcome this difficulty. Semaphores can be used to solve various synchronization
problems and can be implemented efficiently, especially if hardware support for
atomic operations is available.
Various
different synchronization problems (such as the bounded-buffer problem, the readers-writers
problem, and the dining-philosophers problem) are important mainly because they
are examples of a large class of concurrency-control problems. These problems are
used to test nearly every newly proposed synchronization scheme.
The operating
system must provide the means to guard against timing errors. Several language constructs
have been proposed to deal with these problems. Critical regions can be used to
implement mutual-exclusion and arbitrary-synchronization problems safely and efficiently.
Monitors provide the synchronization mechanism for sharing abstract data types.
A condition variable provides a method for a monitor procedure to block its execution
until it is signaled to continue.
A transaction
is a program unit that must be executed atomically; that is, either all the operations
associated with it are executed to completion, or none are performed. To ensure
atomicity despite system failure, we can use a write-ahead log. All updates are
recorded on the log, which is kept in stable storage.
If a system
crash occurs, the information in the log is used in restoring the state of the updated
data items, which is accomplished with the use of the undo and redo operations. To reduce the overhead in searching the log after a system failure has occured,
we can use a checkpoint scheme.
When several
transactions overlap their execution, the resulting execution may no longer be equivalent
to an execution where these transactions executed atomically. To ensure correct
execution, we must use a concurrency-control schemes that ensure serializability
by either delaying an operation or aborting the transaction that issued the operation.
The most common ones are locking protocols and timestamp-ordering schemes.
Reference: Operating System Concepts by Silberschatz, Galvin, and Gagne, 2003