When I was child, our family lived about 60 kilometers away from the city. Once or twice a month we go to the city which took about an hour and a half one way. On the way to the city, we have to pass a stretch of very narrow road that was at the side of a very deep cliff. Many people have already lost their lives in that cliff when the bus they rode plunged into the cliff. In order to prevent this, a mechanism was setup that allowed only one bus at a time to enter that section of road, whether going one way or the other. In order for each end of the road to determine if a bus is traversing at any moment, they used a telephone to signal the other party so that the other buses can wait before taking their turn. It was always a frightening experience each time we pass that road. In computer science, we can view the dangerous narrow section of road as a *critical section* and the buses traversing this road as the *processes*. To guarantee mutual exclusion, the telephone system that signals each end of the road is called a * semaphore*.

We see semaphores everyday. The traffic light is a semaphore that protects the vehicles from bumping each other in that critical section of the road called the intersection. Semaphores are also used in the navy. Below is an example of a semaphore used in the US Navy.

In the last post, we talked about concurrency and an algorithm to ensure correctness of concurrent algorithms. In this post, we will present another mechanism to ensure correctness of concurrent programs. That mechanism is called a semaphore and was first proposed by Edsger Dijkstra. A semaphore* is a data structure S that is composed of an integer V and a set L and being modified by two atomic functions. The first function is called `wait`

and is defined as follows:

wait(S) if S.V > 0 S.V <- S.V -1 else S.L <- S.L union p p.state <- blocked

The `wait`

function is called by a process on a semaphore before it enters the critical section. If the value of S.V is not zero, S.V is decremented by 1 and the process is allowed to continue its execution. Otherwise, the process is blocked and is put on the set S.L.

The `signal`

function is defined as

signal(S) if S.L = empty set S.V <- S.V + 1 else select a process q in S.L S.L <- S.L - {q} q.state <- ready

When a process is done in its critical section, it will call the `signal`

operation on the semaphore S. If the set S.L has no blocked processes, it will increment the value of S.V by 1, otherwise a process is selected from the set S.L and its state is set to “ready”.

To use the semaphore construct, a typical concurrent program will be written like the one below:

# loop forever noncriticalSection() wait(S) criticalSection() signal(S)

**Simple Properties of Semaphores**

In this section, we list some simple properties of semaphores that we can use in proving correctness. By properties, we mean those mathematical properties that remain invariant under whatever state of the computation.

1. From the definition of the `wait`

and `signal`

functions, we can see that the value of S.V never goes negative, that is

2. If initially , then we can compute the subsequent values of S.V by counting the number of calls to `wait`

and `signal`

(with some qualifications). If processes call the `wait`

function, then the value of S.V goes down to 0. If all of these processes call the `signal`

function, then the value of S.V goes back to k. If however, a process calls `wait`

and the value of S.V is already 0, then S.V will just remain zero and the process is blocked. We say that the call to `wait`

has failed an we will not count this call. If another process calls on `signal`

and seeing that S.L is not empty, the S.V value will remain the same. This call to `signal`

is also not counted. Having laid down the rules for counting the calls to `wait`

and `signal`

, we can see that

where and are the number of calls to `wait`

and `signal`

, respectively.

3. At anytime, the number of processes executing the critical section is found by counting the number of processes entering the critical section by a successful call to `wait`

, minus the number of processes leaving the critical section by a successful call to `signal`

:

**Correctness of the Semaphore Solution for Two Processes**

As we have discussed in the last post, to prove correctness we need to show that the algorithm is mutually exclusive, free from deadlock and starvation.

1. *Mutually Exclusion* – Let S.V = 1 initially. At anytime during the execution, the number of processes in the critical section is given by property 3 above, that is, . From property 2,

Since by property 1 , therefore . That is, there can be at most one process in the critical section at anytime.

2. *Freedom from deadlock* – Two processes can deadlock if the value of S.V = 0 and neither are in the critical section (). By property 2 and 3,

Since S.V = 0 and , substituting this to the equation above gives us

,

a contradiction

3. *Freedom from Starvation* – Suppose a process , upon calling `wait`

, sees that , and is blocked to starvation in S.L. By property 2 and 3,

which means that the other process is in the critical section. By assumption of progress, process will eventually call `signal`

and will pick one process from S.L to enter the critical section. Since there is only one process that is blocked, and that is , process can then enter its critical section, contradicting the assumption that it is starved.

* For more information, consult the book “Principles of Concurrent and Distributed Programming” by M. Ben-Ari.