JavaScript promise rejection: Loading CSS chunk katex failed. (error: https://git.marcorealacci.me/assets/css/katex.94d26766.css). Open browser console to see more details.
master-degree-notes/Concurrent Systems/notes/4 - Semaphores.md

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 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