16/10/20 

## Mutexes

**Mutexes** are an approach for mutual exclusion **provided by the operating system** containing a Boolean lock variable to indicate availability.

> The lock variable is set to **true** if the lock is available
>
> Two atomic functions are used to **manipulate the mutex**
>
> 1. `acquire()` - called **before**  entering a critical section, Boolean set to **false**
> 2. `release()` - called **after** exiting the critical section, Boolean set to **true** again.

```c
acquire() {
	while(!available); // busy wait
	available = false;
}
```

```c
release() { 
    available = true; 
}
```

`acquire()` and `release()` must be **atomic instructions** .

* No **interrupts** should occur between reading and setting the lock.
* If interrupts can occur, the follow sequence could occur.

```c
T_i => lock available
... 								T_j => lock available
... 								T_j sets lock
T_i sets lock 						...
```

The process/thread that acquires the lock must **release the lock** - in contrast to semaphores.

| Pros                                                         | Cons                                                         |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| Context switches can be **avoided**.                         | Calls to `acquire()` result in **busy waiting**. Shocking performance on single CPU systems. |
| Efficient on multi-core systems when locks are **held for a short time**. | A thread can waste it's entire time slice busy waiting.      |

![img](/lectures/osc/assets/S.png)



## Semaphores

> **Semaphores** are an approach for **mutual exclusion** and **process synchronisation** provided by the operating system.
>
> * They contain an **integer variable**
> * We distinguish between **binary** (0-1) and **counting semaphores** (0-N)
>
> Two **atomic functions** are used to manipulate semaphores** 
>
> 1. `wait()` - called when a resource is **acquired** the counter is decremented.
> 2. `signal()` / `post()` is called when the resource is **released**.
>
> **Strictly positive values** indicate that the semaphore is available, negative values indicate the number of processes/threads waiting.

```c
//Definition of a Semaphore
typedef struct {
	int value;
	struct process * list;
} semaphore;
```

```c
wait(semaphore * S) {
	S->value--;
	if(S->value < 0) {
		add process to S->list
		block(); // system call
    }
}
```

```c
post(semaphore * S) {
	S->value++;
	if (S->value <= 0) {
        remove a process P from S->list;
        wakeup(P); // system call
	}
}
```

```
Thread N
...
...
wait(&s)
...
(wakeup)
...
post(&s)
```

Calling `wait()` will **block** the process when the internal **counter is negative** (no busy waiting)

1. The process **joins the blocked queue**
2. The **process/thread state** is changed from running to blocked
3. Control is transferred to the process scheduler.

Calling `post()` **removes a process/thread** from the blocked queue if the counter is less than or equal to 0.

1. The process/thread state is changed from ***blocked** to **ready**
2. Different queuing strategies can be employed to **remove** process/threads e.g. FIFO etc

The negative value of the semaphore is the **number of processes waiting** for the resource.

`block()` and `wait()` are system called provided by the OS.

`post()` and `wait()` **must** be **atomic**

> Can be achieved through the use of mutexes (or disabling interrupts in a single CPU system)
>
> Busy waiting is moved from the **critical section** to `wait()` and `post()` (which are short anyway - the original critical sections themselves are usually much longer)

We must lock the variable `value`.

```c
post(semaphore * S) {
    // lock the mutex
	S->value++;
	if (S->value <= 0) {
        remove a process P from S->list;
        wakeup(P); // system call
	}
    //unlock mutex
}
```

Semaphores put your code to sleep. Mutexes apply busy waiting to user code.

Semaphores within the **same process** can be declared as **global variables** of the type `sem_t`

> * `sem_init()` - initialises the value of the semaphore.
> * `sem_wait()` - decrements the value of the semaphore.
> * `sem_post()` - increments the values of the semaphore.

![img](/lectures/osc/assets/t.png)

Synchronising code does result in a **performance penalty**

> * Synchronise only **when necessary**
>
> * Synchronise as **few instructions** as possible (synchronising unnecessary instructions will delay others from entering their critical section)

```c
void * calc(void * increments) {
    int i, temp = 0;
    for(i = 0; i < *((int*) increments);i++) {
    	temp++;
    }
    sem_wait(&s);
    sum+=temp;
    sem_post(&s);
}
```

**Figure**: optimised `calc` function

#### Starvation

> Poorly designed **queueing approaches** (e.g. LIFO) may results in fairness violations

#### Deadlock

> Two or more processes are **waiting indefinitely** for an event that can be caused only by one of the waiting processes or thread.

#### Priority Inversion

> Priority inversion happens when a high priority process (`H`) has to wait for a **resource** currently held by a low priority process (`L`)
>
> Priority inversion can happen in chains e.g. `H` waits for `L` to release a resource and L is interrupted by a medium priority process `M`. 
>
> This can be avoided by implementing priority inheritance to boost `L` to the `H`'s priority.



## The Producer and Consumer Problem

> * Producer(s) and consumer(s) share N **buffers** (an array) that are capable of holding **one item each** like a printer queue.
>   * The buffer can be of bounded (size N) or **unbounded size**.
>   * There can be one or multiple consumers and or producers.
> * The **producer(s)** add items and **goes to sleep** if the buffer is **full** (only for a bounded buffer)
> * The **consumer(s)** remove items and **goes to sleep** if the buffer is **empty**

The simplest version of this problem has **one producer**, **one consumer** and a buffer of **unbounded size**.

* A counter (index) variable keeps track of the **number of items in the buffer**.
* It uses **two binary semaphores:**
  * `sync` **synchronises** access to the **buffer** (counter) which is initialised to 1.
  * `delay_consumer` ensures that the **consumer** goes to **sleep** when there are no items available, initialised to 0.

![img](/lectures/osc/assets/U.png)

It is obvious that any manipulations of count will have to be **synchronised**.

**Race conditions** will still exist:

> When the consumer has **exhausted the buffer** (when `items == 0`), it should go to sleep but the producer increments `items` before the consumer checks it.
>
> * Consumer has removed the **last element**
> * The producer adds a **new element**
> * The consumer should have gone to sleep but no longer will
> * The consumer consumes **non-existing elements**
>
> **Solutions**:
>
> * Move the consumers' if statement inside the critical section



### Producers and Consumers Problem

The previous code (one consumer one producer) is made to work by **storing the value of** `items` 

A different variant of the problem has `n` consumers and `m` producers and a fixed buffer size `N`. The solution is based on **3 semaphores**

1. `sync` - used to **enforce mutual exclusion** for the buffer

2. `empty` - keeps track of the number of empty buffers, initialised to `N`

3. `full` - keeps track of the number of **full buffers** initialised to 0.

The `empty` and `full` are **counting semaphores** and represent **resources**.

![img](/lectures/osc/assets/V.png)
