3.2 KiB
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 MUTEXIN/OUT
: two variables pointing to locations inBUF
to (circularly) insert/remove items, both initialized at 0FREE/BUSY
: two semaphores that count thew number of free/busy cells ofBUF
, 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