Open main menu
Home
Random
Recent changes
Special pages
Community portal
Preferences
About Wikipedia
Disclaimers
Incubator escapee wiki
Search
User menu
Talk
Dark mode
Contributions
Create account
Log in
Editing
Semaphore (programming)
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
{{about|the computer programming term|other uses|Semaphore (disambiguation)}} {{short description|Variable used in a concurrent system}} In [[computer science]], a '''semaphore''' is a [[variable (programming)|variable]] or [[abstract data type]] used to control access to a common resource by multiple [[process (computing)|threads]] and avoid [[critical section]] problems in a [[concurrent computing|concurrent]] system such as a [[Computer multitasking|multitasking]] operating system. Semaphores are a type of [[Synchronization (computer science)|synchronization primitive]]. A trivial semaphore is a plain variable that is changed (for example, incremented or decremented, or toggled) 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'' (i.e., to avoid [[Race condition|race conditions]]) 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 (or locked/unlocked, unavailable/available) are called '''binary semaphores''' and are used to implement [[Lock (computer science)|locks]]. The semaphore concept was invented by [[Dutch people|Dutch]] [[computer scientist]] [[Edsger Dijkstra]] in 1962 or 1963,<ref name="ReferenceA">{{Cite EWD|35|Over de sequentialiteit van procesbeschrijvingen}} (undated, 1962 or 1963)</ref> 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 [[Receptionist|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 for as long as desired, and so it is not possible to book rooms ahead of time. 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,<ref name="LittleBookOfSemaphores">[http://greenteapress.com/semaphores/ ''The Little Book of Semaphores''] Allen B. Downey</ref> they are forced to wait until a room is freed (when the count is increased from 0). If one of the rooms was released, but there are several students waiting, then any method can be used to select the one who will occupy the room (like [[FIFO (computing and electronics)|FIFO]] or randomly picking one). And of course, a student must inform the clerk about releasing their room only after really leaving it. ===Important observations=== When used to control access to a [[pool (computer science)|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 (possibly involving more semaphores) 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 (which practically means a program may behave slowly, act erratically, [[hang (computing)|hang]], or [[crash (computing)|crash]]) 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 (or after releasing it). Even if all processes follow these rules, ''multi-resource [[deadlock (computer science)|deadlock]]'' may still occur when there are different resources managed by different semaphores and when processes need to use more than one resource at a time, as illustrated by the [[dining philosophers problem]]. ==Semantics and implementation== Counting semaphores are equipped with two operations, historically denoted as P and V (see {{section link||Operation names}} for alternative names). Operation V increments the semaphore ''S'', and operation P decrements it. The value of the semaphore ''S'' is the number of units of the resource that are currently available. The P operation [[busy waiting|wastes time]] or [[Sleep (system call)|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 {{mono|wait}} (P) and {{mono|signal}} (V) operations is: * {{mono|wait}}: Decrements the value of the semaphore variable by 1. If the new value of the semaphore variable is negative, the process executing {{mono|wait}} is blocked (i.e., added to the semaphore's queue). Otherwise, the process continues execution, having used a unit of the resource. * {{mono|signal}}: Increments the value of the semaphore variable by 1. After the increment, if the pre-increment value was negative (meaning there are processes waiting for a resource), it transfers a blocked process from the semaphore's waiting queue to the ready queue. Many operating systems provide efficient semaphore primitives that unblock a waiting process when the semaphore is incremented. This means that processes do not waste time checking the semaphore value unnecessarily. 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 operation]]s, i.e., operations that appear indivisible to other processes: '''function''' V(semaphore S, integer I): [S ← S + I] '''function''' P(semaphore S, integer I): '''repeat:''' ['''if''' S ≥ I: S ← S − I '''break'''] However, the rest of this section refers to semaphores with unary V and P operations, unless otherwise specified. To avoid [[Resource starvation|starvation]], a semaphore has an associated [[queue (data structure)|queue]] of processes (usually with [[FIFO (computing and electronics)|FIFO]] semantics). 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-write|read, modify, and write]] the semaphore in a single operation. Without such a hardware instruction, an atomic operation may be synthesized by using a [[Mutual exclusion#Software solutions|software mutual exclusion algorithm]]. On [[uniprocessor]] systems, atomic operations can be ensured by temporarily suspending [[preemption (computing)|preemption]] or disabling hardware [[interrupt]]s. 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|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 (''S'') just before a train station (''A''). In this case, if the signal is green, then one can enter the train station. If it is yellow or red (or any other color), the train station cannot be accessed. ===Login queue=== Consider a system that can only support ten users (S=10). 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 (login slots available), 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 (the producer) generates data items and another process (the consumer) 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. The semaphore solution to the producer–consumer problem tracks the state of the queue with two semaphores: <code>emptyCount</code>, the number of empty places in the queue, and <code>fullCount</code>, the number of elements in the queue. To maintain integrity, <code>emptyCount</code> may be lower (but never higher) than the actual number of empty places in the queue, and <code>fullCount</code> may be lower (but never higher) 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 <code>emptyCount</code> and <code>fullCount</code> maintain control over these resources. The binary semaphore <code>useQueue</code> 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 <code>emptyCount</code> is initially ''N'', <code>fullCount</code> is initially 0, and <code>useQueue</code> is initially 1. The producer does the following repeatedly: '''produce:''' P(emptyCount) P(useQueue) putItemIntoQueue(item) V(useQueue) V(fullCount) The consumer does the following repeatedly '''consume:''' P(fullCount) P(useQueue) item ← getItemFromQueue() V(useQueue) V(emptyCount) Below is a substantive example: # A single consumer enters its critical section. Since <code>fullCount</code> is 0, the consumer blocks. # Several producers enter the producer critical section. No more than ''N'' producers may enter their critical section due to <code>emptyCount</code> constraining their entry. # The producers, one at a time, gain access to the queue through <code>useQueue</code> and deposit items in the queue. # Once the first producer exits its critical section, <code>fullCount</code> is incremented, allowing one consumer to enter its critical section. Note that <code>emptyCount</code> 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 <code>useQueue</code> before filling empty places. Note that <code>emptyCount + fullCount ≤ ''N''</code> always holds, with equality if and only if no producers or consumers are executing their critical sections. ===Passing the baton pattern=== The "Passing the baton" pattern<ref>{{cite book |last1=Andrews |first1=Gregory R. | date=1999|publisher= Addison-Wesley|title=Foundations of Multithreaded, Parallel, and Distributed Programming}}</ref><ref>{{cite book |last1=Carver |first1=Richard H. |last2=Thai |first2=Kuo-Chung |date=2005|publisher=Wiley|title=Modern Multithreading: Implementing, Testing, and Debugging Multithreaded Java and C++/Pthreads/Win32 Programs}}</ref><ref>{{cite book |last1=Maurer |first1=Christian | date=2021|publisher= Springer|title=Nonsequential and Distributed Programming with Go}}</ref> proposed by Gregory R. Andrews is a generic scheme to solve many complex concurrent programming problems in which multiple processes compete for the same resource with complex access conditions (such as satisfying specific priority criteria or avoiding starvation). Given a shared resource, the pattern requires a private "priv" semaphore (initialized to zero) for each process (or class of processes) involved and a single mutual exclusion "mutex" semaphore (initialized to one). The pseudo-code for each process is: <syntaxhighlight lang="cpp"> void process(int proc_id, int res_id) { resource_acquire(proc_id, res_id); <use the resource res_id>; resource_release(proc_id, res_id); } </syntaxhighlight> The pseudo-code of the resource acquisition and release primitives are: <syntaxhighlight lang="cpp"> void resource_acquire(int proc_id, int res_id) { P(mutex); if(<the condition to access res_id is not verified for proc_id>) { <indicate that proc_id is suspended for res_id>; V(mutex); P(priv[proc_id]); <indicate that proc_id is not suspended for res_id anymore>; } <indicate that proc_id is accessing the resource>; pass_the_baton(); // See below } </syntaxhighlight> <syntaxhighlight lang="cpp"> void resource_release(int proc_id, int res_id) { P(mutex); <indicate that proc_id is not accessing the resource res_id anymore>; pass_the_baton(); // See below } </syntaxhighlight> Both primitives in turn use the "pass_the_baton" method, whose pseudo-code is: <syntaxhighlight lang="cpp"> void pass_the_baton(int res_id) { if <the condition to access res_id is true for at least one suspended process> { int p = <choose the process to wake>; V(priv[p]); } else { V(mutex); } } </syntaxhighlight> '''Remarks''' The pattern is called "passing the baton" because a process that releases the resource as well as a freshly reactivated process will activate at most one suspended process, that is, shall "pass the baton to it". The mutex is released only when a process is going to suspend itself (resource_acquire), or when pass_the_baton is unable to reactivate another suspended process. ==Operation names== The canonical names V and P come from the initials of [[Dutch language|Dutch]] words. V is generally explained as ''verhogen'' ("increase"). Several explanations have been offered for P, including ''proberen'' ("to test" or "to try"),<ref>{{citation |last1 = Silberschatz |first1 = Abraham | last2 = Galvin | first2 = Peter Baer | last3 = Gagne | first3 = Greg | edition = 8th | isbn = 978-0-470-12872-5 | publisher = John Wiley & Sons. Inc | title = Operating System Concepts | year = 2008 |page=234}}</ref> ''passeren'' ("pass"), and ''pakken'' ("grab"). Dijkstra's earliest paper on the subject<ref name="ReferenceA"/> gives ''passering'' ("passing") as the meaning for ''P'', and ''vrijgave'' ("release") as the meaning for V. It also mentions that the terminology is taken from that used in railroad signals. Dijkstra subsequently wrote that he intended ''P'' to stand for ''prolaag'',<ref>{{Cite EWD|74}}</ref> short for ''probeer te verlagen'', literally "try to reduce", or to parallel the terms used in the other case, "try to decrease".<ref>{{Cite EWD|51| MULTIPROGAMMERING EN DE X8}} (in [[Dutch language|Dutch]])</ref><ref name="try-and">Dijkstra's own translation reads "try-''and''-decrease", although that phrase might be confusing for those unaware of the [http://www.wsu.edu/~brians/errors/try.html colloquial "try-and..."]</ref><ref>[https://lkml.org/lkml/2005/12/19/34 (PATCH 1/19) MUTEX: Introduce simple mutex implementation] Linux Kernel Mailing List, 19 December 2005</ref> In [[ALGOL 68]], the [[Linux kernel]],<ref>[http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html#ss5.3 Linux Kernel hacking HOWTO] {{Webarchive|url=https://web.archive.org/web/20100528154351/http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html#ss5.3 |date=2010-05-28 }} LinuxGrill.com</ref> and in some English textbooks, the ''V'' and ''P'' operations are called, respectively, ''up'' and ''down''. In software engineering practice, they are often called ''signal'' and ''wait'',<ref name="plan9">{{cite conference |last1=Mullender |first1=Sape |first2=Russ |last2=Cox |title=Semaphores in Plan 9 |conference=3rd International Workshop on [[Plan 9 from Bell Labs|Plan 9]] |year=2008 |url=http://doc.cat-v.org/plan_9/IWP9/2008/iwp9_proceedings08.pdf#page=62}}</ref> ''release'' and ''acquire''{{r|plan9}} (standard [[Java (programming language)|Java]] library),<ref>{{Javadoc:SE|package=java.util.concurrent|java/util/concurrent|Semaphore}}</ref> or ''post'' and ''pend''. Some texts call them ''vacate'' and ''procure'' to match the original Dutch initials.<ref>{{Cite web|url=http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node036A.html|title=exec.library/Procure|website=amigadev.elowar.com|access-date=2016-09-19}}</ref><ref>{{Cite web|url=http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node0389.html|title=exec.library/Vacate|website=amigadev.elowar.com|access-date=2016-09-19}}</ref> ==Semaphores vs. mutexes== A [[mutex]] is a [[Mutual exclusion#Types of mutual exclusion devices|locking mechanism]] that sometimes uses the same basic implementation as the binary semaphore. However, they differ in how they are used. While a binary semaphore may be colloquially referred to as a mutex, a true mutex has a more specific use-case and definition, in that only the [[Task (computing)|task]] that locked the mutex is supposed to unlock it. This constraint aims to handle some potential problems of using semaphores: # [[Priority inversion]]: If the mutex knows who locked it and is supposed to unlock it, it is possible to promote the priority of that task whenever a higher-priority task starts waiting on the mutex. # Premature task termination: Mutexes may also provide deletion safety, where the task holding the mutex cannot be accidentally deleted. {{citation needed|date=May 2019}} (This is also a cost; if the mutex can prevent a task from being reclaimed, then a garbage collector has to monitor the mutex.) # Termination deadlock: If a mutex-holding task terminates for any reason, the [[Real-time operating system|OS]] can release the mutex and signal waiting tasks of this condition. # Recursion deadlock: a task is allowed to lock a [[reentrant mutex]] multiple times as it unlocks it an equal number of times. # Accidental release: An error is raised on the release of the mutex if the releasing task is not its owner. ==See also== * [[Async/await]] * [[Flag (programming)]] * [[Synchronization (computer science)]] * [[Cigarette smokers problem]] * [[Dining philosophers problem]] * [[Readers–writers problem]] * [[Sleeping barber problem]] * [[Monitor (synchronization)|Monitor]] * [[Spurious wakeup]] ==References== {{Reflist}} ==External links== === Introductions === * Hilsheimer, Volker (2004). "[https://doc.qt.io/archives/qq/qq11-mutex.html Implementing a Read/Write Mutex]" (Web page). ''Qt Quarterly'', Issue 11 - Q3 2004 * {{cite journal |url= https://see.stanford.edu/materials/icsppcs107/23-Concurrency-Examples.pdf |first1= Julie |last1= Zelenski |first2= Nick |last2= Parlante |series= CS107 Programming Paradigms |volume= Spring 2008 |issue= 23 |journal= Handout |title= Thread and Semaphore Examples |publisher= Stanford Engineering Everywhere (SEE) }} === References === *{{Cite EWD|123|Cooperating sequential processes}} (September 1965) *{{citation |mode=cs1 |url=http://www.opengroup.org/onlinepubs/009695399/basedefs/semaphore.h.html |section=semaphore.h - semaphores (REALTIME) |title=The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition |publisher=Open Group |date=2004}} *{{cite web |url= http://greenteapress.com/semaphores/ |title= The Little Book of Semaphores |first= Allen B. |last= Downey |publisher= Green Tea Press |issue= 2.2.1 |edition= 2nd |date= 2016 |orig-year= 2005 }} *{{cite web |url= https://www.mv.helsinki.fi/home/joleppaj/jleppaja_gradu_080511.pdf |title= A pragmatic, historically oriented survey on the universality of synchronization primitives |first= Jouni |last= Leppäjärvi |date= May 11, 2008 |location= University of Oulu, Finland }} {{Data types}} {{Design patterns}} {{Inter-process communication}} {{Concurrent computing}} {{Parallel computing}} {{DEFAULTSORT:Semaphore (Programming)}} [[Category:Synchronization|Computer science]] [[Category:Concurrency control]] [[Category:Concurrency (computer science)]] [[Category:Parallel computing]] [[Category:Computer-mediated communication]] [[Category:Edsger W. Dijkstra]] [[Category:Dutch inventions]]
Edit summary
(Briefly describe your changes)
By publishing changes, you agree to the
Terms of Use
, and you irrevocably agree to release your contribution under the
CC BY-SA 4.0 License
and the
GFDL
. You agree that a hyperlink or URL is sufficient attribution under the Creative Commons license.
Cancel
Editing help
(opens in new window)
Pages transcluded onto the current version of this page
(
help
)
:
Template:About
(
edit
)
Template:Citation
(
edit
)
Template:Citation needed
(
edit
)
Template:Cite EWD
(
edit
)
Template:Cite book
(
edit
)
Template:Cite conference
(
edit
)
Template:Cite journal
(
edit
)
Template:Cite web
(
edit
)
Template:Concurrent computing
(
edit
)
Template:Data types
(
edit
)
Template:Design patterns
(
edit
)
Template:Inter-process communication
(
edit
)
Template:Javadoc:SE
(
edit
)
Template:Mono
(
edit
)
Template:Parallel computing
(
edit
)
Template:R
(
edit
)
Template:Reflist
(
edit
)
Template:Section link
(
edit
)
Template:Short description
(
edit
)
Template:Webarchive
(
edit
)