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

4.4 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 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»)