Files
notes/docs/lectures/osc/04_concurrency1.md
T
2026-10-04 15:24:17 +01:00

163 lines
6.2 KiB
Markdown

12/10/20
1. Threads and processes execute concurrently or in parallel and can **share resources** (like devices, memory, variables, data structures etc)
2. A process/thread can be interrupted at any point in time. The process "state" (including registers) is saved in the **process control block**
The outcome of programs may be unpredictable:
> Sharing data can lead to **inconsistencies**.
>
> The **outcome of execution** may **depend on the order** in which instructions are carried out.
```c
#include <stdio.h>
#include <pthread.h>
int counter = 0;
void * calc(void * number_of_increments) {
int i;
for(i = 0; i < *((int*) number_of_increments);i++)
counter++;
}
int main() {
int iterations = 50000000;
pthread_t tid1,tid2;
pthread_create(&tid1, NULL, calc, (void *) &iterations);
pthread_create(&tid2, NULL, calc, (void *) &iterations);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("The value of counter is: %d\n", counter);
}
```
This piece of code creates two threads and points them towards the `calc` function. The `pthread_join(tid1,NULL);` line waits until thread 1 has finished before the code moves on.
`counter++` consists of three separate actions.
1. *read* the value of counter from memory and **store it in a register**
2. *add* one to the value in the register
3. *store* the value of the register **in counter** in memory
The above actions are **not** "atomic". This means they can be interrupted by the timer.
TCB - *Thread Control Block*
However the thread control block could be out of date by the time the thread starts running again. For example *counter* could be 2 but the thread control block still has the old value of *counter*.
The problem is that simple instructions in C are actually multiple instructions in assembly code. Another example is `print()`.
```c
void print() {
chin = getchar();
chout = chin;
putchar(chout);
}
```
If the two threads are **interleaved** one after the other, there is no issue.
![img](assets/Q.png)
However, if **interleaved** like this they do interact. The global variable used to store the character in thread 1, is overwritten when thread 2 runs. This means 1+1+1 = 2
## Bounded Buffer
> Consider a **bounded buffer** in which N items can be stored
>
> A **counter** is maintained to count the number of items currently in the buffer. It is **incremented** when something is added and **decremented** when an item is removed.
>
> Similar **concurrency problems** as with the calculation of sums happen in the bounded buffer which is a consumer problem.
```c
// producer
while (true) {
//while buffer is full
while (counter == BUFFER SIZE); /* do nothing */
// Produce item
buffer[in] = new_item;
in = (in + 1) % BUFFER_SIZE;
counter++;
}
// consumer
while (true) {
// wait until items in buffer
while (counter == 0); /* do nothing */
// Consume item
consumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
counter--;
}
```
This is a circular queue: there's a start and end pointer (*in* and *out*). The shared counter is being manipulated from two different places, which can go wrong.
## Race Conditions
A **race condition occurs** when multiple threads/processes **access shared data** and the result is dependent on **the order in which the instructions are interleaved**.
### Concurrency within the OS
> **Kernels are pre-emptive**
> **Multiple processes/threads are running** in the kernel.
> Kernel processes can be **interrupted** at any point.
>
> The kernel maintains **data structures**
>
> 1. These data structures are accessed **concurrently.**
> 2. These can be subject to **concurrency issues.**
>
> The OS must make sure that interactions within the OS do not result in race conditions.
>
> Processes **share resources** including memory, files, processor time, printers etc.
>
> The OS must:
>
> 1. provide **locking mechanisms** to implement **mutual exclusion** and **prevent starvation and deadlocks.**
> 2. Allocate and deallocate these resources safely.
A **critical section** is a set of instructions in which **shared resources** between processes/threads **are changed**.
**Mutual exclusion** must be enforced for **critical sections**.
> Only **one process at a time** should be in the critical section (mutual exclusion)
>
> Processes have to **get "permission"** before entering their critical section
>
> 1. Request a lock
> 2. Hold the lock
> 3. Release the lock
Any solution to the **critical section problem** must satisfy the following requirements:
1. **Mutual exclusion** - only one process can be in its critical section at any one point in time.
2. **Progress** - any process must be able to enter its critical section at some point in time. (A process/thread has a right to enter its critical section at a point in time.) If there is no thread/process in the critical section, there is no reason for the current thread not to be allowed in the **critical section**.
3. **Fairness/bounded waiting** - fairly distributed waiting times/processes cannot be made to wait indefinitely.
These requirements have to be satisfied independently of the order in which sequences are executed.
### Enforcing Mutual Exclusion
**Approaches** for mutual exclusion can be:
1. Software based - Peterson's solution
2. Hardware based - `test_and_set()` `swap_and_comapare()`
Deadlocks have to be prevented as well.
#### Deadlock Example
A set of processes/threads is *deadlocked* if each process/thread in the set is waiting for an event that only the other process/thread in the set can cause.
Each **deadlocked process/thread** is waiting for a resource held by another deadlocked process/thread (which cannot run and hence release the resource).
- Assume that X and Y are **mutually exclusive resources**.
- Threads A and B need to **acquire both resources** and request them in opposite orders.
**Four conditions** must hold for a deadlock to occur
1. **Mutual exclusion** - a resource can be assigned to at most one process at a time.
2. **Hold and wait condition** - a resource can be held while requesting new resources.
3. **No pre-emption** - resources cannot be forcefully taken away from a process
4. **Circular wait** - there is a circular chain of two or more processes waiting for a resource held by the other processes.
**No deadlocks** can occur if one of the conditions isn't met.