master-degree-notes/Concurrent Systems/notes/4 - Semaphores.md

156 lines
No EOL
4.4 KiB
Markdown

**Object:** entity with an implementation (hidden) and an interface (visible), made up of a set of operations and a specification of the behavior.
**Concurrent:** if the object can be accessed by different processes.
**Semaphore:** is a shared counter S accessed via primitives $up$ and $down$ s.t.:
- is initialized at s0 >= 0
- it is alwayz >= 0
- *up* atomically increases S
- *down* atomically decreases S if it is not 0, otherwise the calling process is blocked and waits.
Main use: **prevent busy waiting**: suspend processes that cannot perform *down*.
**Strong semaphore:** if uses a FIFO policy for blocking/unblocking, otherwise it's **weak**.
**Binary semaphore:** if it is at most 1 (also *up* are blocking).
A semaphore needs two underlying objects:
- a counter initialized at s0 that can also become negative (see the implementation below to understand)
- a data structure (typically a queue), initially empty, to store suspended processes.
#### Ideal implementation
```
S.down() :=
S.counter--
if S.counter < 0 then
enter into S.queue
SUSPEND
return
S.up() :=
S.counter++
if S.counter <= 0 then
activate a proc from S.queue
return
```
>[!note] note
>if S.counter ≥ 0, then this is the value of the semaphore; otherwise, S.counter tells you how many processes are suspended in S
>
>all operations are in MUTEX
#### Actual implementation
```
Let t be a test&set register initialized at 0
S.down() :=
Disable interrupts
wait S.t.test&set() = 0
S.counter--
if S.counter < 0 then
enter into S.queue
S.t <- 0
Enable interrupts
SUSPEND
else
S.t <- 0
Enable interrupts
return
S.up() :=
Disable interrupts
wait S.t.test&set() = 0
S.counter++
if S.counter <= 0 then
activate a proc from S.queue
S.t <- 0
Enable interrupts
return
```
Same as before but we use test&set to actually ensure MUTEX (of course we could have used any other hardware MUTEX implementation seen so far).
#### (Single) Producer/Consumer
It's a shared FIFO buffer of size k.
- `BUF[0,…,k-1]`: generic registers (not even safe) accessed in MUTEX
- `IN/OUT` : two variables pointing to locations in `BUF` to (circularly) insert/remove items, both initialized at 0
- `FREE/BUSY`: two semaphores that count thew number of free/busy cells of `BUF`, initialized at k and 0 respectively.
```
B.produce(v) :=
FREE.down() # blocking if FREE becomes negative
BUF[IN] <- v
IN <- (IN+1) mod k # mod k since it is circular
BUSY.up() # "there is something to consume"
return
B.consume() :=
BUSY.down() # it blocks if there is nothing to consume
tmp <- BUF[OUT]
OUT <- (OUT+1) mod k
FREE.up()
return tmp
```
> [!note] Downside
> Reading from / writing into the buffer can be very expensive!
#### (Multiple) Producers/Consumers
**Accessing BUF in MUTEX slows down the implementation**, we would like to have the possibility to read and write from different cells **in parallel**.
- we use two arrays FULL and EMPTY of atomic boolean registers, initialized at ff (all zeros) and tt (all ones), respectively
- we have two extra semaphores SP and SC, both initialized at 1
```
B.produce(v) :=
FREE.down()
SP.down()
while not EMPTY[in] do
IN <- (IN+1) mod k
i <- IN
EMPTY[IN] <- ff
SP.up()
BUF[i] <- v
FULL[i] <- tt
BUSY.up()
return
B.consume() :=
BUSY.down()
SC.down()
while not FULL[OUT] do
OUT <- (OUT+1) mod k
o <- OUT
FULL[OUT] <- ff
SC.up()
tmp <- BUF[o]
EMPTY[o] <- tt
FREE.up()
return tmp
```
Thanks to the semaphores, we are sure that while loops will not go on forever! The loops starts only if there is at least a FREE / BUSY cell.
#### (Multiple) Producers/Consumers - Wrong solution
EXERCISE - will do later
#### The Readers/Writers problem
- Several processes want to access a file
- Readers may simultaneously access the file
- At most one writer at a time
- Reads and writes are mutually exclusive
>[!note] Remark
>this generalizes the MUTEX problem (MUTEX = RW with only writers)
The read/write operations on the file will all have the following shape:
```
conc_read() :=
begin_read()
read()
end_read()
conc_write() :=
begin_write()
write()
end_write()
```
##### Weak priority to Readers
- If a reader arrives during a read, it can surpass possible writers already suspended (risk of starvation for the writes)
- When a writer terminates, it activates the first suspended process, irrispectively of whether it is a reader or a writer (so, the priority to readers is said «weak»)