Unsorted

TODO Merge this into the correct chapters.

Semaphore Implementation with no Busy Waiting

TODO

Typedef struct {
    int value;
    struct process *list;
    } semaphore;

wait(semaphore *S) {
    S -> value--;
    if (S -> value < 0) {
        // add this process to S -> list
        sleep();
    }
}

signal(semaphore *S) {
    if (S -> value <= 0) {
        // remove process from S -> list
        wakeup(P);
    }
}

Basic system calls used:

Issues with Semaphores

Incorrect use of sempahore operations can cause deadlock and starvation.

Example: Misusing Semaphores

Interchanging Order

TODO

Replaces signal with wait

TODO

omits signal, wait, or both

TODO

Monitors

High-level ADT that encapsulates data along with functions though which the data may be accessed and manipulated

Purpose: Guarantee mutual exclusion.

monitor monitor_name
{
    // shared vars
    procedure P1 (...) {...}
    ...
    procedure Pn (...) {...}
        initialization code (...) {...}
}

Deadlock & Starvation

Deadlock: Two or more processes wait indefinitely for an event that can only be caused by only one of the waiting processes.

Starvation: Indefinite blocking

Priority Inversion: Scheduling problem when lower-priority process holds a lock needed by a higher-priority process.

Indivisibility: When we say a critical section must be indivisibly/atomically, we are talking about the code which holds and releases the lock.

Priority Inversion Protocol: We temporarily assign the highest priority to the process currently executing in its critical section to prevent it getting starved.

Classical Problems of Synchronization

Bounded Buffer Problem

Suppose you have n buffers, each can hold one item.

TODO Code

do {
    ...
    // produce iteme
    ...

}

Producer:

Consumer:

TODO Increment and decrement rules?

Idea? — To prevent deadlock, we have each process manage a dedicated boolean? notfull and notempty? How does that owrk?

Readers and Writers

In this problem, a dataset if shared among a number of concurrent processes.

Problem: How do you let multiple readers read at the same time?


Variations:

Note — Both may have starvation, leading to even more variations.


Solution to First Variation

Shared Data:

The idea basically, is the first reader will check if it’s safe to get into the CS (wait(rw_mutex)). After this, all the readers won’t have to wait for the rw_mutex, they’ll only ever wait to update the read_count. The last reader will release the lock and allow the writer to do its thing again.

TODO Code

Solution via Monitor

Suppose you have a monitor:

TODO Define reader-writer Monitor

Dining Philosophers

Problem

Philosophers spend their lives alternating between thinking and eating.

In the case of 5 philosophers sitting in a circle (with a single chopstick in-between each):

Issue: If every philosopher picks up a chopstick, they will get into deadlock.

Naive “Solution”

One naive “solution” is to represent each chopstick with a semaphore.

    semaphore chopstick[5];
    loop {
// Wait
        wait(chopstick[i]); // (left)
        wait(chopStick[(i \text{$+$} 1) % 5]); // (right)
// eat...

// Release
        signal(chopstick[i]);
        signal(chopStick[(i \text{$+$} 1) % 5]);
// think...
    }

Problem: If every single philosopher grabs their left chopstick at once (this is valid), this’ll result in deadlock.

/s — Unrelated, but one optimization is to put “think” at the start of the loop so that the philosophers think before they wait. But personally I don’t want them to think on an empty stomache.

Some Solutions

1. Allow at most four philosophers to be sitting simultaneously at the table.

2. Make the act of picking up both chopsticks a critical section (e.g., wrap in mutex).

Example: Grab two chopsticks at once.

1. Obvious Solution

  semaphore chopstick[5];
  loop {
// Wait
      P(mutex);
      wait(chopstick[i]); // (left)
      wait(chopStick[(i \text{$+$} 1) % 5]); // (right)
      V(mutex);
// eat...

// Release
      signal(chopstick[i]);
      signal(chopStick[(i \text{$+$} 1) % 5]);
// think...
  }

Issues: No synchronization (no defense against lockout).

2.

  semaphore chopstick[5];
  loop {
// Wait
      P(wait(chopstick[min(i, (i \text{$+$} 1) % 5]);
      P(wait(chopstick[max(i, (i \text{$+$} 1) % 5]);
// eat...

// Release
      signal(chopstick[i]);
      signal(chopStick[(i \text{$+$} 1) % 5]);
// think...
  }

This has the same synchronization issue.

TODO How does this avoid deadlock(?).

3.

TODO Monitor example

3. Asymmetric; odd-numbered philosophers pick up left-then-right chopstick, evens vice-versa.

4.

Deadlock

When a process is waiting for something that’ll never happens.

Deadlock arises if four conditions hold simultaneously:

  1. Mutual Exclusion: Only one process at a time can use a resource.
  2. Hold and Wait: A process holds one process, and waits for another resource(s) to be available.
  3. No Preemption: A resource can only by release voluntarily by the process holding it.
  4. Circular Wait: There exists at set { P_0,P_1,...,P_n } of waiting processes such that P_0 is waiting for a resource held by P_1, P_1 is waiting for a resource held by P_2, etc.

Example — Dining Philosophers demonstrates all four properties.

Resource Allocation Graph

Set of vertieces V and edges E.

Example TODO

Deadlock Detection

If a resource allocation graph has a cycle, it may have a deadlock, specifically:

TODO Graphs

Handling Deadlock

  1. Ensure system doesn’t get deadlocked
  2. Allow system to enter deadlock, detect it, and recover from it.
  3. Ignore problem and pretend deadlocks never occur.

Real-World: #3 is used by most systems (UNIX, Windows, and Linux).

If we can break any of the four conditions, we can prevent deadlocks.

  1. Breaking Mutual Exclusion: Make this not-required for shareable resources (e.g., read-only files).
  2. Breaking Hold and Wait: Guarantee that when a process requests a resource, it doesn’t hold any other resources.
    1. Method 1: Process must request and be allocated all its resources before it begins execution, or
    2. Method 2: Process can only request resources when it has none allocated to it (e.g., it must deallocate before it can request more)
  3. Breaking No Preemption: If a process is holding some resources and request another resource that cannot be immediately allocated, all its resources are released.
  4. Breaking Circular Wait: Impose a total ordering of all resource types, and require that each process requests resources in an increasing order of enumeration.

More on Issues:

Deadlock Avoidance

Requests that the system has some additional a priori information available.

Simplest and most-useful model: Each process must declare the maximum number of resources of each type that it may need.

Basically, we must be careful when allocating resources, to prevent circular waits.

On Safe State:

Example: Deadlock Avoidance

Suppose a system with 12 resources.

ProcessAllocatedMaximum Needs
T_0510
T_124
T_229

Our current availability is 12-9=3, but the processes could request up to 14 resources.

Suppose all the processes request their maximum resources.

How can we allocate resources without causing a deadlock?

The crux of it is that we must fulfill processes while thinking about the resources we would get back by getting them to finish.

(The correct way to serve in this situation would be T_1, T_0, T_2)

Avoidance Algorithms

Resource Allocation Graph

Use: Single-instances of resources.

TODO Drawing of Resource Allocation Graph with Claim Edge.

We introduce the claim edge, which says a process will use a process in the future.

On Behavior:

Central Idea — Requests to convert request edges to assignment edges are only served if they don’t result in the formation of a cycle.

Bankers Algorithm

Use: Multiple-instances of resources.

When a process gets all its resources, it must return them in a finite amount of time.

Data Structure: We use an array and three n \times m matrices to do banker’s algorithm.

TODO Algorithm, described using data structure.

Baically, every time a request is made, we run a huge calculation to see if this would leave us in a safe state.

TODO

TODO Wait-for graph? Detecting Deadlock??

Recovering from Deadlock

Abort Deadlocked Processes

Danger — Aborting a process, especially in its critical section, is dangerous (e.g., corrupting a file mid-write)

Abort All

Abort all deadlocked processes. This guarantees the end of deadlock.

Abort One at a Time

You can abort one deadlocked process at a time, checking if the deadlock cycle is eliminated.

Recovery from Deadlock

Resource Preemption

Alternatively, you can do resource preemption to break one of the four criteria of deadlock.

Costs — It’s costly to repeatedly:

  1. Select a victim,
  2. Rollback to a safe state, and
  3. Ensure you aren’t starving (repeatedly targeting the same process).