Controlling access to shared data to avoid race conditions
A system typically consists of several threads running either concurrently or in parallel. Threads often share user data. Meanwhile, the operating system continuously updates various data structures to support multiple threads.
Race condition
Situation where access to shared data is not controlled, possibly resulting in corrupt data values
Process synchronization involves using tools that control access to shared data to avoid race conditions.
These tools must be used carefully, as their incorrect use can result in poor system performance, including deadlock.
Cooperating processes can either directly share a logical address space (that is, both code and data) or be allowed to share data only through shared memory or message passing.
Concurrent access to shared data may result in data inconsistency.
Race condition
Situation where several processes access and manipulate the same data concurrently and the outcome of the execution depends on the particular order in which the access takes place
Race condition when assigning a pid
Two processes, P0 and P1, are creating child processes using the system call fork(). There is a race condition on the kernel variable which represents next available pid, the value of the next available process identifier. Unless mutual exclusion is provided, it is possible the same process identifier number could be assigned to two separate processes.
Critical-section problem
Designing a protocol that processes can use to synchronize their activity so as to cooperatively share data
Requirements for solution to critical-section problem
Mutual exclusion
Progress
Bounded waiting
The critical-section problem could be solved simply in a single-core environment if we could prevent interrupts from occurring while a shared variable was being modified
Synchronization tools
Software-based solutions like the Peterson's solution are not guaranteed to work on modern computer architectures. As such, three major hardware instructions provide support for solving the critical-section problem.
Mutex locks
Used to protect critical sections and thus prevent race conditions. A process must acquire the lock before entering a critical section; it releases the lock when it exits the critical section.
Acquire() and release()
Functions to acquire and release a mutex lock
Calls to either acquire() or release() must be performed atomically.
Spinlock
Mechanism used to achieve short duration lock
The main disadvantage of implementing the mutex lock is that it requires busy waiting.
Semaphores
An integer variable that is accessed only through two standard atomic operations: signal() and wait()
Wait()
Decrements the semaphore value. If the value becomes negative, the process is blocked.
Signal()
Increments the semaphore value. If the value is negative, a blocked process is unblocked.
Counting semaphore
Can range over an unrestricted domain
Binary semaphore
Can range only between 0 and 1. Behave similarly to mutex locks.
Using semaphores for synchronization
Two concurrently running processes: P1 with a statement S1 and P2 with a statement S2. Require that S2 be executed only after S1 has completed. Implement this using a shared semaphore synch, initialized to 0.
Rather than engaging in busy waiting, the process is suspended when waiting on a semaphore.
Semaphore definition
Each semaphore has an integer value and a list of processes. When a process must wait on a semaphore, it is added to the list of processes. A signal() operation removes one process from the list of waiting processes and awakens that process.
It is critical that semaphore operations be executed atomically.
In a multicore environment, interrupts must be disabled on every processing core to ensure atomicity of semaphore operations.
Disadvantages of semaphores
Incorrect implementation can result in timing errors
Priority inversion
Deadlock
Incorrect implementation of semaphore can result in timing errors that are difficult to detect, since these errors happen only if particular execution sequences take place.
Monitor
An abstract data type that includes a set of programmer-defined operations that are provided with mutual exclusion within the monitor
Condition variable
Variables of type condition that can be used to define tailor-made synchronization schemes. The only operations that can be invoked on a condition variable are wait() and signal().