Semaphore (programming)
In computer science, a semaphore is a variable or abstract data type used to control access to a common resource by multiple threads and avoid critical section problems in a concurrent system such as a multitasking operating system. Semaphores are a type of synchronization primitive. A trivial semaphore is a plain variable that is changed depending on programmer-defined conditions.
A useful way to think of a semaphore as used in a real-world system is as a record of how many units of a particular resource are available, coupled with operations to adjust that record safely as units are acquired or become free, and, if necessary, wait until a unit of the resource becomes available.
Though semaphores are useful for preventing race conditions, they do not guarantee their absence. Semaphores that allow an arbitrary resource count are called counting semaphores, while semaphores that are restricted to the values 0 and 1 are called binary semaphores and are used to implement locks.
The semaphore concept was invented by Dutch computer scientist Edsger Dijkstra in 1962 or 1963, when Dijkstra and his team were developing an operating system for the Electrologica X8. That system eventually became known as the THE multiprogramming system.
Library analogy
Suppose a physical library has ten identical study rooms, to be used by one student at a time. Students must request a room from the front desk. If no rooms are free, students wait at the desk until someone relinquishes a room. When a student has finished using a room, the student must return to the desk and indicate that the room is free.In the simplest implementation, the clerk at the front desk knows only the number of free rooms available. This requires that all of the students use their room while they have signed up for it and return it when they are done. When a student requests a room, the clerk decreases this number. When a student releases a room, the clerk increases this number. The room can be used as long as desired and rooms cannot be booked in advance.
In this scenario, the front desk count-holder represents a counting semaphore, the rooms are the resource, and the students represent processes/threads. The value of the semaphore in this scenario is initially 10, with all rooms empty. When a student requests a room, they are granted access, and the value of the semaphore is changed to 9. After the next student comes, it drops to 8, then 7, and so on. If someone requests a room and the current value of the semaphore is 0, they are forced to wait until a room is freed. If one of the rooms was released, but there are several students waiting, any method can be used to select the one who will occupy the room. And of course, a student must inform the clerk about releasing their room only after leaving it.
Important observations
When used to control access to a pool of resources, a semaphore tracks only how many resources are free. It does not keep track of which of the resources are free. Some other mechanism may be required to select a particular free resource.The paradigm is especially powerful because the semaphore count may serve as a useful trigger for a number of different actions. The librarian above may turn the lights off in the study hall when there are no students remaining, or may place a sign that says the rooms are very busy when most of the rooms are occupied.
The success of the protocol requires applications to follow it correctly. Fairness and safety are likely to be compromised if even a single process acts incorrectly. This includes:
- requesting a resource and forgetting to release it;
- releasing a resource that was never requested;
- holding a resource for a long time without needing it;
- using a resource without requesting it first.
Semantics and implementation
Counting semaphores are equipped with two operations, historically denoted as P and V. Operation V increments the semaphore S, and operation P decrements it.The value of the semaphore S represents the number of units of available resource units when non-negative. In some implementations, negative values indicate the number of processes waiting for the resource. The P operation wastes time or sleeps until a resource protected by the semaphore becomes available, at which time the resource is immediately claimed. The V operation is the inverse: it makes a resource available again after the process has finished using it.
One important property of semaphore S is that its value cannot be changed except by using the V and P operations.
A simple way to understand and operations is:
- : Decrements the value of the semaphore variable by 1. If the new value of the semaphore variable is negative, the process executing is blocked. Otherwise, the process continues execution, having used a unit of the resource.
- : Increments the value of the semaphore variable by 1. After the increment, if the pre-increment value was negative, it transfers a blocked process from the semaphore's waiting queue to the ready queue.
The counting semaphore concept can be extended with the ability to claim or return more than one "unit" from the semaphore, a technique implemented in Unix. The modified V and P operations are as follows, using square brackets to indicate atomic operations, i.e., operations that appear indivisible to other processes:
function V:
function P:
repeat:
However, the rest of this section refers to semaphores with unary V and P operations, unless otherwise specified.
To avoid starvation, a semaphore has an associated queue of processes. If a process performs a P operation on a semaphore that has the value zero, the process is added to the semaphore's queue and its execution is suspended. When another process increments the semaphore by performing a V operation, and there are processes on the queue, one of them is removed from the queue and resumes execution. When processes have different priorities the queue may be ordered thereby, such that the highest priority process is taken from the queue first.
If the implementation does not ensure atomicity of the increment, decrement, and comparison operations, there is a risk of increments or decrements being forgotten, or of the semaphore value becoming negative. Atomicity may be achieved by using a machine instruction that can read, modify, and write the semaphore in a single operation. Without such a hardware instruction, an atomic operation may be synthesized by using a software mutual exclusion algorithm. On uniprocessor systems, atomic operations can be ensured by temporarily suspending preemption or disabling hardware interrupts. This approach does not work on multiprocessor systems where it is possible for two programs sharing a semaphore to run on different processors at the same time. To solve this problem in a multiprocessor system, a locking variable can be used to control access to the semaphore. The locking variable is manipulated using a test-and-set-lock command.
Examples
Trivial example
Consider a variable A and a Boolean variable S. A is only accessed when S is marked true. Thus, S is a semaphore for A.One can imagine a stoplight signal just before a train station. In this case, if the signal is green, then one can enter the train station. If it is yellow or red, the train station cannot be accessed.
Login queue
Consider a system that can only support ten users. Whenever a user logs in, P is called, decrementing the semaphore S by 1. Whenever a user logs out, V is called, incrementing S by 1 representing a login slot that has become available. When S is 0, any users wishing to log in must wait until S increases. The login request is enqueued onto a FIFO queue until a slot is freed. Mutual exclusion is used to ensure that requests are enqueued in order. Whenever S increases, a login request is dequeued, and the user owning the request is allowed to log in. If S is already greater than 0, then login requests are immediately dequeued.Producer–consumer problem
In the producer–consumer problem, one process generates data items and another process receives and uses them. They communicate using a queue of maximum size N and are subject to the following conditions:- the consumer must wait for the producer to produce something if the queue is empty;
- the producer must wait for the consumer to consume something if the queue is full.
emptyCount, the number of empty places in the queue, and fullCount, the number of elements in the queue. To maintain integrity, emptyCount may be lower than the actual number of empty places in the queue, and fullCount may be lower than the actual number of items in the queue. Empty places and items represent two kinds of resources, empty boxes and full boxes, and the semaphores emptyCount and fullCount maintain control over these resources.The binary semaphore
useQueue ensures that the integrity of the state of the queue itself is not compromised, for example, by two producers attempting to add items to an empty queue simultaneously, thereby corrupting its internal state. Alternatively a mutex could be used in place of the binary semaphore.The
emptyCount is initially N, fullCount is initially 0, and useQueue is initially 1.The producer does the following repeatedly:
produce:
P
P
putItemIntoQueue
V
V
The consumer does the following repeatedly
consume:
P
P
item ← getItemFromQueue
V
V
Below is a substantive example:
- A single consumer enters its critical section. Since
fullCountis 0, the consumer blocks. - Several producers enter the producer critical section. No more than N producers may enter their critical section due to
emptyCountconstraining their entry. - The producers, one at a time, gain access to the queue through
useQueueand deposit items in the queue. - Once the first producer exits its critical section,
fullCountis incremented, allowing one consumer to enter its critical section.
emptyCount may be much lower than the actual number of empty places in the queue, for example, where many producers have decremented it but are waiting their turn on useQueue before filling empty places. Note that emptyCount + fullCount ≤ N always holds, with equality if and only if no producers or consumers are executing their critical sections.