6 – Synchronization

Concurrent Execution

Examples of unsynchronized access that may lead to non-deterministic outcome (based on order of access/modification, aka race conditions.)

Solving race conditions

Critical section: does critical work accessing shared modifiable resources. At any point only one program can execute in that critical section.

Implementing Critical Sections

Incorrect Synchronization

Implementations

Assembly level

TestAndSet( Register, MemLocation )

void EnterCS (int *lock) {
	/* i.e. while the CS process has set *MemLoc to 1,
	 * other processes trying to set access is stuck in while loop
     * as TestAndSet returns 1. */
	while (TestAndSet( lock ) == 1);
}

void ExitCS (int *lock) {
	*lock = 0;
}

Problems: busy waiting wastes processing power.

Solution: variants of the instructions.

Variants: Compare and Exchange, Atomic Swap, Load Link/Store Conditional

High-level Language

  1. Simulate “Test and Set”, but non-atomic. We can try preventing interleaving by disabling interrupts, but

High level Test-and-Set

  1. Turn array. While Turn != P.turn, busy wait.

Take **turns** to enter critical section.

  1. Want array. Busy wait while someone else wants the turn.

Wait until no-one else **wants** the turn

  1. Combine Turn and Want: Peterson’s algorithm

Peterson's Algorithm

We assume that line 2 (writing to Turn in each process) is atomic.

High-level Abstraction

Semaphores:

Generalized sync mechanism.

Contains one int representing capacity,

Invariant for semaphore $s$:

\[s_\text{curr} = s_\text{init} + \# \text{signals executed}(s) - \# \text{waits completed}(s)\]

Mutex:

Binary semaphores with capacity of 1.

Number of processes in critical section: $N_{CS} = \text{waits completed}(s) - \text{signals executed}(s)$​​

\[s_\text{curr} = \textcolor{blue}{1 - N_{CS}} \geq 0 \Rightarrow \textcolor{red}{N_{CS} \leq 1}\]

No deadlock where $N_{CS} = s_\text{curr} = 0$​​​, granted correct usage.

No starvation if fair scheduling.

Conditional variable:

How to wait for another thread to do something specific?

Note: after a thread is woken up, the condition may not hold anymore.

Monitor:

A collection of procedures manipulating a shared data structure, one lock must be held when accessing the shared data.

Synchronization patterns

From The Little Book of Semaphores.

Rendezvous

How to ensure `a1` before `b2`, `b1` before `a2`

# Thread A
semA = 0
statement a1
signal(semA)
wait(semB)
statement a2

# Thread B
semB = 0
statement b1
signal(semB)
wait(semA)
statement b2

Barrier

rendezvous // all n-1 threads block until nth thread reaches rendezvous
critical point

$n$ threads. No thread executes critical point until all have reached rendezvous.

wait(mutex)
	waitingThreads += 1
	if (waitingThreads == n) signal(barrier) -- (1)
signal(mutex)

wait(barrier)
signal(barrier) -- (2)

critical point

(1) Triggers first waiting thread to pass through. Now value of barrier is $-(n-2)$​​.

(2) You must signal(barrier) after the each thread is done waiting for the barrier, otherwise the next in queue will not be able to execute (deadlock). This pattern of wait(b), signal(b) is also called a turnstile.

Reusable barrier

How to lock the barrier again after all the threads have passed?

wait(mutex)
	waitingThreads += 1
	if (waitingThreads == n) 
		wait(t2) -- wait for nth thread to unlock 2nd turnstile
		signal(t1) -- signals that the 1st turnstile is unlocked
signal(mutex)

wait(t1)
signal(t1)

critical point

wait(mutex)
	waitingThreads -= 1
	if (waitingThreads == 0) 
		wait(t1) -- wait for nth thread to unlock 1st turnstile
		signal(t2) -- signals that the 2nd turnstile is unlocked
signal(mutex)

wait(t2)
signal(t2)

Two-phase barrier. Note we cannot solve this problem with a single turnstile, e.g.

Wrong reusable barrier

If the $n$​th thread passes through the CP, decrements count without interruption and then returns to the rendezvous (in a loop), and increments count again, it restores the count to $n$ and thus gets ahead of the other threads.

Classical synchronization problems

Producers-consumers

Shared bounded buffer of size $K$​. Constraints are:

# Producers: notFull initialized to K
wait(notFull) -- (1)
	wait(mutex)
        buffer[in] = item
        in = (in+1) % K
	signal(mutex)
signal(notEmpty)

# Consumers: notEmpty initialized to 0
wait(notEmpty) -- (1)
	wait(mutex)
        item = buffer[out]
        out = (out-1) % K
	signal(mutex)
signal(notFull)
use item	

(1) Note this is a rendezvous. Removes the need to check the item count in that buffer.

Readers-writers

Example of categorical mutual exclusion. Readers don’t exclude other threads, but writers do. Constraints:

# Writers: notEmpty initialized to 1
wait(openToWriters)
	/* prioritization 
	 * wait(noWriters)
	 * 	write
	 * signal(noWriters)
	 */
signal(openToWriters)

# Readers
wait(mutex)
	readerCount += 1
	if (readerCount == 1) 
		wait(openToWriters) -- (1)
signal(mutex)
read
/* prioritization: 
 * signal(noWriters)
 */
wait(mutex)
	readerCount -= 1
	if (readerCount == 0) signal(openToWriters)
return(mutex)
signal

(1) Prior to this step, if readerCount == 1 then wait until all writers gone (openToWriters)

Drawbacks: starvation of writer possible.

Solution: Priority to writers. (see /* prioritization */ snippets)

Dining philosophers

Image Specification
- 5 philosophers seated around a table.
- 5 single chopsticks between them
- To eat, one must acquire his left and right chopstick
- Deadlock-free and starve-free to allow philosophers to freely eat

Base solution

Simple solutions

Simple informal argument (for “One right-hander”. Let R be the right hander):

  1. If R grabs left: R can eat (no deadlock)
  2. If R tries to grab left but it is taken: the philosopher to R’s left has grabbed his right and is eating (no deadlock)
  3. If R tries to grab right but it is taken: Many situations but in the worst case scenario everyone else has picked up their left chopstick except R. i.e. philosopher to R’s left has his right chopstick free and can start eating (no deadlock)

Tanenbaum solution

Tanenbaum solution

Take chopsticks Safe to eat Put chopsticks