96 lines
No EOL
2.7 KiB
Markdown
96 lines
No EOL
2.7 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** |