Sie sind auf Seite 1von 68

Concurrency / Semaphore

Page 1 of 1

Site Map
0. What Is This Website All About? 1. Introduction
1.1 Motivation 1.2 Everyday Examples 1.3 Definition

2. Implementation
2.1 Implementation of Semaphores 2.2 Classical Problems 2.2.1 Sleeping Barber 2.2.2 Cigarette Smokers 2.2.3 Unisex Bathroom 2.3 POSIX Library 2.3.1 POSIX Mutexes 2.3.2 POSIX Semaphores 2.4 Case Study on Prex Semaphore

3. Advantages and Disadvantages 4. Alternatives 5. Conclusion

http://www.comp.nus.edu.sg/~phanquyt/cs2106/sitemap.html

11/11/2007

Concurrency / Semaphore

Page 1 of 2

[ Up: Site Map | Next: 1. Introduction ]

0. What Is This Website All About?


Geek 1: Huh, s-e-m-a, sema-p-h-o-r-e, semaphore, man this is hard. Whats that all about? Geek 2: Dunno leh, but using my great powers of intuition, I guess its some sort of musical instrument, like saxophone, la. Geek 1: Yeah lor, I guess so. Makes sense. Thanks aa.. Were you thinking the same as well, semaphore = musical instrument? WELL WE HOPE NOT!! In a sentence, a semaphore, is a tool that has given birth to todays world of efficient multitasking, which portrays an illusion of parallelism. Geek Geek Geek Geek 1: Oh really? Wah..very nice. 2: Yeah yeah, I too agree, nice nice. 1: But I have a question, what is multitasking? 2: Me too, same question..

Multitasking, well, taking the analogy of the MRT in Singapore; for example, imagine that there is a bridge (resource) connecting to ends of a railway track, and two trains( process) on opposite sides, facing each other, who want to use the particular stretch of track to continue on their journey. It is not possible for both of the trains to cross at the same time as they would collide. This is where a station master (semaphore) comes in, to co-ordinate events (multitask). Hence, multitasking is, a state when multiple tasks a.k.a. processes want to use a common resource.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/overview.html

11/11/2007

Concurrency / Semaphore

Page 2 of 2

Geek 1: Ah I get it. Geek 2: Me too! Geek 1: So is that it, just helps trains cross? Well semaphores have helped resolve some of the problems that operating system have suffered from. Examples of such problems are: Synchronization Race Condition Mutual Exclusion Starvation Deadlock Livelock Geek 1: Wa liao!! Dun understand. Geek 2: Nehmin, they will explain. Yes we would! With pleasure. To get things started, we would guide you from the conception of semaphore in 1965, by the erratic and undoubtedly one of the greatest geniuses in the field, Edsger Dijkstra, to the present day, where certain tweaks have been made. So what marked the beginning of all this, it was the era of the swinging sixties. To let yourself get enveloped in the fun, exciting and remarkably logical world of multiprogramming, please read ahead. Hope you have as much fun reading, as we did compiling! Have a blast!! [ Up: Site Map | Next: 1. Introduction ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/overview.html

11/11/2007

Concurrency / Semaphore

Page 1 of 1

[ Previous: 0. What Is This Website All About? | Up: Site Map | Next: 1.1. Motivation ]

1. Introduction
In this introduction to semaphore, you will be guided through three different aspects to have a rough but good understanding of semaphore. You will see the motivation to invent such a technique to solve computing problems, daily examples analogical to semaphore and finally the formal definition. Begin your journey by clicking on the destinations below. 1.1 Motivation 1.2 Everyday Examples 1.3 Definition

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro.html

11/11/2007

Concurrency / Semaphore

Page 1 of 2

[ Previous: 0. What Is This Website All About? | Up: 1. Introduction | Next: 1.2. Everyday Examples ]

1.1 Motivation
In the olden days, semaphores were basically physical devices with colored lights and arms to signal the drivers of trains to prevent crashing from each other or to send complex messages by sailors at night by opening and closing the shutters of the lantern that they used. Thus these semaphores were devices to prevent mutual exclusion (two things cannot happen at the same time) happening in real life. So why the need for semaphores when people were happy signaling one another to get the job done? Well like so many things today, it all started in the swinging sixties. The 1960s was a time of dynamic and radical, social, political and technological change. The era was marked with the birth of the hippies, gay and feminist movements in USA, opposition to the Vietnam war, rapid rise of the New left, rampant drug use, the ever growing hostile cold war, and the period also a marked a very important contribution to computer science. People had a race for using computers, and the processors had to restrict their access to shared resources. This gave rise to the problem of mutual exclusion in the computing environment. Edsger Dijkstra, found a new way of tackling this problem by involving semaphores. His semaphore was not a physical device but a variable which can be accessed by different operations. In his paper on THE, the system which implemented the idea of semaphores from a software perspective, for the first time, he had mentioned that one of the main motivations for the use of semaphores was to reduce the complexity involved when it came to programming which dealt with mutual exclusion.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_motivation.html

11/11/2007

Concurrency / Semaphore

Page 2 of 2

You would be surprised at the penetration level of semaphores. You can find their use from libraries to aviation systems. The next section illustrates different cases in which semaphores are used. [ Previous: 0. What Is This Website All About? | Up: 1. Introduction | Next: 1.2. Everyday Examples ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_motivation.html

11/11/2007

Concurrency / Semaphore

Page 1 of 3

[ Previous: 1.1 Motivation | Up: 1. Introduction | Next: 1.3. Definition ]

1.2 Everyday Examples


Earlier we had a look at the train, which uses the concept of semaphores. The following examples would illustrate the same, however, although, they may seem similar, the technicalities involved are different. We would have a look at the beautiful and intricate world of it in the implementation. For now content yourself with the elegant simplicity of the idea, that has helped resolve a lot of otherwise major barriers. This particular example looks at the implementation of semaphores in a library. Scenario: Consider a situation where students reading CS2106 decide to use the book Modern Operating Systems as their main reference just before their final exam. But alas there is only one copy of it in the library and unfortunately NUS is going through a major maintenance in its library website at that time, so no electronic helps to check whether the book is available. Problem: How is it possible for a student to check the availability of a book, without having to physically climb up or down the stairs, into the dungeon of books, and getting disappointed? You would hate having to do that, wont you? Solution: A very bright lad/lass doing the course suggested the following in which the information desk, where the librarian sits would be used as a flag, to inform the students on the books status: 1. The librarian first makes a list of people who want the book on a first come first served basis. She also sets the available status of the book for others to see on a slate. If the available status is 1

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_examples.html

11/11/2007

Concurrency / Semaphore

Page 2 of 3

then the book is available and if its 0, book is not available. 2. The first student comes to borrow the book. 3. He sees that the book is available from the slate, as the librarians list is empty initially. 4. He checks out the book and on the slate he sets available=0 to inform others. 5. Now a second student who wants to use the book would check the status from the slate. 6. He finds that the book is not available so the librarian enters his name in her list. 7. Now the first student who borrowed it earlier finishes using the book, he returns the book and then immediately changes the available status of the book to 1. Hence the second student who is next in the librarians list can now access the book and he repeats the above process. Here the available information that is given to other students about the status of the book acts like a semaphore. The slate acts as the signal to students. The next example ought to get you a bit interested in semaphores, or at least appreciate it more. Trust us on this, you would want to read, your future investments and millions depend on this. Scenario: Consider a case where Angelina Jolie has a million dollars in her account and she now wants to deposit another million dollars. At the same time Brad Pitt wants to withdraw a million dollars from Jolies account. Both are trying to access the same account at the same time from different ATMs. There are two possibilities: 1. If Jolies request is processed first, then the balance in her account is updated to 2 million dollars. Then Brads request is

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_examples.html

11/11/2007

Concurrency / Semaphore

Page 3 of 3

processed 1 million is deducted and now the balance again becomes to 1 million dollars. 2. If Brads request is processed first, then the balance in Jolies account is deducted to 0 dollars. Then Jolies request is processed and now the balance becomes to 1 million dollars. Here we can see that there is competition for using the Bank database (storage) and the result depends on which request is processed first. But both of them give out the correct result, i.e. one million dollars. Problem: Consider that these events happen alternatively: 1. Jolie tries to deposit 1 million dollars. The system downloads the previous account balance (i.e. 1 million) from the bank database and adds the deposit amount, i.e. another one million dollars. 2. But before updating the bank balance the system starts handling Brads request of withdrawing 1 million dollars. 3. Now again the system downloads the previous account balance (i.e.1 million) from the bank database and deducts the withdrawal amount. First the system handles Jolies request to update her account balance to 2 million dollars (1 million + 1 million). Now the system handles Brads request to update Jolies account to 0 dollars (1 million - 1 million). Final result: Angelina Jolie is left with 0 dollars!!! What was the problem here? Well two processes, i.e. Brad and Jolie, both having access to the same account, at the same time, i.e, mutual exclusion. So what is the solution, well the answer to that lies in the next section. [ Previous: 1.1 Motivation | Up: 1. Introduction | Next: 1.3. Definition ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_examples.html

11/11/2007

Concurrency / Semaphore

Page 1 of 2

[ Previous: 1.2 Everday Examples | Up: 1. Introduction | Next: 2. Implementation ]

1.3 Definition
A semaphore is: 1. A hardware or software flag used to indicate the status of some activity. 2. A shared space for inter-process communications (IPC) controlled by "wake up" and "sleep" commands. The source process fills a queue and goes to sleep until the destination process uses the data and tells the source process to wake up. Dijkstra introduced semaphore, S, with two operations, V and P. V(S) increases the value of S by 1 in an atomic (or indivisible) operation and may wake one of the waiting processes up. P(S) checks the value of S. If S is greater than 0, P(S) decreases the value of S by 1 in an atomic operation. Otherwise, the calling process goes to sleep and waits until S is greater than 0. In addition, there is an operation which takes a parameter and initializes a semaphore to that value. The following example illustrates a scenario where the problem of sharing one printer (I/O) is resolved using semaphore. (In this case, we assume the printer does not have a queue for print jobs.) Please click START to begin.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_definition.html

11/11/2007

Concurrency / Semaphore

Page 2 of 2

If a semaphore only takes two values, 0 and 1, it is called a mutex. In this case, a binary semaphore or mutex, is always initialized to 1. And, now that we have got ourselves acquainted with semaphores, strap your seatbelts, for we are going to take you on a fun filled journey of verhoog and probeer. Sounds like cheese doesnt it? Well you are in for a surprise. [ Previous: 1.2 Everday Examples | Up: 1. Introduction | Next: 2. Implementation ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/intro_definition.html

11/11/2007

Concurrency / Semaphore

Page 1 of 1

[ Previous: 1.3. Definition | Up: Site Map | Next: 2.1. Implementation of Semaphores ]

2. Implementation
2.1 Implementation of Semaphores 2.2 Classical Problems 2.2.1 Sleeping Barber 2.2.2 Cigarette Smokers 2.2.3 Unisex Bathroom 2.3 POSIX Library 2.3.1 POSIX Mutexes 2.3.2 POSIX Semaphores 2.4 Case Study on Prex Semaphore

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation.html

11/11/2007

Concurrency / Semaphore

Page 1 of 4

[ Previous: 1.3 Definition | Up: 2. Implementation | Next: 2.2 Classical Problems ]

2.1 Implementation of Semaphores


As mentioned in the previous section, there are two operations on a semaphore, P and V, plus an operation to initialize a semaphore. P and V can also be referred to as increment and decrement, or signal and wait. (It is interesting to note that there is no operation to read the value of a semaphore. Therefore, before we perform the P operation, we would not know whether the calling process will block or not.) The code for these operations is shown below. init(s, value): s = value P(s): // the calling process may go to sleep if s <= 0 wait until s > 0 s-V(s): s++ // may wake one of the waiting processes up P and V must be atomic, or indivisible, operations. In particular, when s is greater than 0, the decrement of s must follow immediately, i.e. no process can be interleaved beween the test s > 0 and the decrement. All of these can be done using the test-and-set instruction or ignoring interrupts. The test-and-set instruction checks the value of a memory location and writes to it if some condition is satisfied; all in an atomic operation. If this instruction is not supported, ignoring interrupts can be used to ensure no other processes can run while P or V is being executed.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 2 of 4

The P operation may invovle busy waiting if it is implemented as a loop continously checking the condition s > 0. In this case, the operation wastes the processor time. One way to avoid this problem is to maintain a queue of processes waiting on each semaphore. If the queue is a FIFO queue, the processes are waken up in the order they are put into the queue. Otherwise, we might not be able to tell which process will be waken up by the V operation. The code for the implementation with queuing is shown below. s = init(1) // s is initialized to 1 P(s): s-if s < 0: the process goes to sleep and is put into the queue semaphore

waiting

for

this

V(s): if s < 0: // if the queue is not empty remove one process from the queue and wake it up s++ It is interesting to note that in this case, the value of the semaphore can be negative, which is impossible for the code shown at the beginning of this section. A value of -n (n > 0) implies there are n processes waiting on the semaphore. s is initialized to 1 to ensure the first process can enters its critical section. Finally, mutex is a binary semaphore. The idea of mutex is mutual exclusion, i.e. only one process can be in the critical section at one time. On the other hand, a semaphore can have a value greater than one. For example, we may allow a certain number of connections in a database system and thus initialize the semaphore to the maximum number of connections allowed.

Spinlocks
Besides Test and Set instructions we also can use another synchronization

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 3 of 4

mechanism called Spinlocks which makes a process simply wait in a loop until the lock becomes available. Because sometimes it is much efficient to constantly poll for the availability of a lock rather than Pre-empting the thread, schedule another thread. Keeping track of which process owns the synchronization object or lock, or how long has the timeout interval elapsed. The additional usage of C function calls or even of Assembler routines for a fair first-in-first-out mechanism. The pseudo code for implementing Spinlocks is as follows: typedef int SpinLock; void InitLock(SpinLock *L) { *L = 0; } void Lock(SpinLock *L) { while (TestAndSet(L)) ; } void UnLock(SpinLock *L) { *L = 0; } The disadvantage of using spinlocks is that, since it involves busy waiting, a lot of CPU time is devoted to it. So these spinlocks are efficient only if the processes are likely to be blocked for a short period of time. Moreover they are certainly unfair as there is no mechanism involved in them to prevent a situation of starvation where a process might just have to wait forever.

References
1. Semaphores (Wikipedia) 2. Process Synchronization: Semaphores 3. Allen B. Downey (2005), The Little Book of Semaphores 4. Spinlocks and Semaphores

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 4 of 4

5. Spinlock (Wikipedia) 6. User-Level Spin Locks [ Previous: 1.3 Definition | Up: 2. Implementation | Next: 2.2 Classical Problems ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 1 of 1

[ Previous: 2.1 Implementation of Semaphores | Up: 2. Implementation | Next: 2.2.1 Sleeping Barber Problem ]

2.2 Classical Problems


2.2.1 Sleeping Barber 2.2.2 Cigarette Smokers 2.2.3 Unisex Bathroom

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_problems.html

11/11/2007

Concurrency / Semaphore

Page 1 of 4

[ Previous: 2.1 Implementation of Semaphores | Up: 2.2 Classical Problems | Next: 2.2.2 Cigarette Smokers ]

2.2.1 Sleeping Barber


Scenario
Consider a barbers shop where there is only one barber, one barber chair and a number of waiting chairs for the customers. When there are no customers the barber sits on the barber chair and sleeps. When a customer arrives he awakes the barber or waits in one of the vacant chairs if the barber is cutting someone elses hair. When all the chairs are full, the newly arrived customer simply leaves.

Problems
There might be a scenario in which the customer ends up waiting on a barber and a barber waiting on the customer, which would result to a deadlock. Then there might also be a case of starvation when the customers dont follow an order to cut hair, as some people wont get a hair cut even though they had been waiting long.

Use of Semaphores
The solution to these problems involves the use of three semaphores out of which one is a mutex (binary semaphore). They are: Customers: Helps count the waiting customers. Barber: To check the status of the barber, if he is idle or not. accessSeats: A mutex which allows the customers to get exclusive access to the number of free seats and allows them to increase or decrease the number. NumberOfFreeSeats: To keep the count of the available seats, so that the customer can either decide to wait if there is a seat free or leave if there are none.

The Procedure

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_barber.html

11/11/2007

Concurrency / Semaphore

Page 2 of 4

When the barber first comes to the shop, he looks out for any customers i.e. calls P (Customers), if there are none he goes to sleep. Now when a customer arrives, the customer tries to get access to the accessSeats mutex i.e. he calls P(accessSeats), thereby setting a lock. If no free seat (barber chair and waiting chairs) is available he releases the lock i.e. does a V(accessSeats) and leaves the shop. If there is a free seat he first decreases the number of free seats by one and he calls V (Customers) to notify the barber that he wants to cut. Then the customer releases the lock on the accessSeats mutex by calling V (accessSeats). Meanwhile when V(Customers) was called the barber awakes. The barber locks the accessSeats mutex as he wants to increase the number of free seats available, as the just arrived customer may sit on the barbers chair if that is free. Now the barber releases the lock on the accessSeats mutex so that other customers can access it to the see the status of the free seats. The barber now calls a V(Barber), i.e. he tells the customer that he is available to cut. The customer now calls a P(Barber), i.e. he tries to get exclusive access of the barber to cut his hair. The customer gets a haircut from the barber and as soon as he is done, the barber goes back to sleep if there are no customers or waits for the next customer to alert him. When another customer arrives, he repeats the above procedure again. If the barber is busy then the customer decides to wait on one of the free waiting seats. If there are no customers, then the barber goes back to sleep.

Implementation
The following pseudo-code guarantees synchronization between barber and customer and is deadlock free, but may lead to starvation of a customer. Customers = init(0) Barber = init(0) accessSeats = init(1) // mutex NumberOfFreeSeats = N //total number of seats The Barber (Thread/Process)

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_barber.html

11/11/2007

Concurrency / Semaphore

Page 3 of 4

while (true) { // runs in an infinite loop P(Customers) // tries to acquire a customer - if none is available he goes to sleep P(accessSeats) // at this time he has been awaken - want to modify the number of available seats NumberOfFreeSeats++ // one chair gets free V(Barber) // the barber is ready to cut V(accessSeats) anymore } The Customer (Thread/Process) P(accessSeats) // tries to get access to the chairs if (NumberOfFreeSeats > 0) { // if there are any free seats NumberOfFreeSeats-- // sitting down on a chair V(Customers) // notify the barber, that there is a customer waiting V(accessSeats) // don't need to lock the chairs anymore P(Barber) // now it's this customers turn, but wait if the barber is busy // here the customer is having his hair cut } else { // there are no free seats // tough luck V(accessSeats) // but don't forget to release the lock on the seats // customer leaves without a haircut } Note: The solution can be modified so that the customers are made to form a queue as they arrive so that the barber can service these customers on a first come first served basis. This solves the problem of starvation where in a customer might have to wait long for his turn to come. // we don't need the lock on the chairs

// here the barber is cutting hair

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_barber.html

11/11/2007

Concurrency / Semaphore

Page 4 of 4

Animation
Please click START to begin.

References
1. Sleeping Barber Problem (Wikipedia) [ Previous: 2.1 Implementation of Semaphores | Up: 2.2 Classical Problems | Next: 2.2.2 Cigarette Smokers ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_barber.html

11/11/2007

Concurrency / Semaphore

Page 1 of 8

[ Previous: 2.2.1 Sleeping Barber | Up: 2.2 Classical Problems | Next: 2.2.3 Unisex Bathroom ]

2.2.2 Cigarette Smokers


Problem
The cigarette smokers problem was first presented by Suhas Patil in 1971. There are three smokers and one agent. A cigarette is made of three ingredients: tobacco, paper and match. Each smoker has infinite supply of one ingredient, e.g. the first one has infinite supply of tobacco, the second one has infinite supply of paper and the last one has infinite of match. The agent has infinite supply of all ingredients. The agent repeatedly chooses two ingredients at random and puts them on the table, and the smoker who has the complementary ingredient can take the two ingredients and make a cigarette to smoke. For example, if the agent puts tobacco and paper on the table, the smoker who has infinite supply of match can make a cigarette. In this problem, the agent represents the operating system and the smokers represent the processes. The operating system should allocate the required resources to the processes and, at the same time, avoid deadlock. We will look at different versions of this problem.

Patil's Version
There are two restrictions on the solution: The agent code cannot be modified Conditional statements and arrays of semaphores are not allowed

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 2 of 8

With these restrictions, this problem is unsolvable. The Patil's version is also called the impossible version. However, while the first restriction makes sense because the operating system's code should not be modified every time we need to solve a problem, the second restriction is not reasonable, according to David Parnas. He called such restriction "artificial" and said that artificial restrictions make many problems unsolvable.

The Trivial Version


In this version, the agent signals the smoker who has the complementary ingredients after putting the two ingredients on the table. This version can be solved quite easily. Initialization agentSemaphore = init(1) // the semaphore for the agent, initialized to 1 for s in smokerSemaphores: // smokerSemaphores array of three semaphores // for three smokers s = init(1) initialized to 1 The agent code while (1): P(agentSemaphore) i, j = chooseIngredients() // choose two ingredients at random putOnTable(i, j) u = findComplimentarySmoker(i, j) // the smoker who has the // complimentary ingredient // all the three is an

semaphores

are

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 3 of 8

V(smokerSemaphores[u]) The code for the first smoker is presented below. The codes for other smokers are similar. while (1): P(smokerSemaphores[0]) makeCigarette() V(agentSemaphore) smoke() The smoker takes the two ingredients on the table, makes a cigarette and wakes up the agent by calling V(agentSemaphore). While this smoker is smoking, the agent continues to put two ingredients on the table. If these ingredients are different from the last two, the agent can wake up another smoker. Otherwise, the smoker who is smoking can make another cigarette after he has finished smoking. In summary, this version is easy to solve because the agent knows who to signal. However, this may not be the case in practice because the operating system may not know the required resources of the processes before hand. We will consider a more interesting version.

The Interesting Version


This version only keeps the first restriction of the Patil's version, which is the agent code cannot be modified. The agent uses four semaphores as follows: agentSemaphore = init(1) // the semaphore for the agent tobaccoSemaphore = init(0) // the semaphore for tobacco on the table paperSemaphore = init(0) // the semaphore for paper on the table

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 4 of 8

matchSemaphore = init(0) // the semaphore for match on the table The agent uses three threads, each of which is for putting two ingredients on the table. When V(agentSemaphore) is called, one of the threads will wake up and put two ingredients on the table. The code for the thread which puts tobacco and paper on the table is presented below. The codes for other threads are similar. while (1): P(agentSemaphore) V(tobaccoSemaphore) V(paperSemaphore)

A Non-Solution
Consider the following codes for the three smokers. while (1): // this smoker has infinite supply of match P(tobaccoSemaphore) P(paperSemaphore) makeCigarette() V(agentSemaphore) smoke()

while (1): // this smoker has infinite supply of paper P(tobaccoSemaphore) P(matchSemaphore) makeCigarette() V(agentSemaphore) smoke()

while (1): // this smoker has infinite supply of tobacco P(paperSemaphore) P(matchSemaphore) makeCigarette() V(agentSemaphore) smoke()

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 5 of 8

This is a non-solution because deadlock can occur. Suppose one agent thread puts tobacco and paper on the table. The smoker with infinite supply of match is blocking on tobacco so he can pick tobacco up. At the same time, the smoker with infinite supply of tobacco is blocking on paper so he can pick paper up. Now the first smoker blocks on paper and the second smoker blocks on match. This is a deadlock. For comparison with the dining philosophers problem, this is similar to the case where all the philosophers pick up the left forks and block on the right forks.

A Solution
The solution below is similar to David Parnas' solution. Parnas' codes are more complex in order to avoid conditional statements. Initialization isTobacco = false // tobacco is not on the table isPaper = false // paper is not on the table isMatch = false // match is not on the table tobaccoSmokerSemaphore = init(0) // the semaphore for the smoker with // infinite supply of tobacco paperSmokerSemaphore = init(0) // the semaphore for the smoker with // infinite supply of paper matchSmokerSemaphore = init(0) // the semaphore for the smoker with // infinite supply of match mutex = init(1) // the mutex used in the codes below This solution uses three threads called pushers to signal the smokers with the

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 6 of 8

complementary ingredient. The code for one pusher is presented below. The codes for the other two pushers are similar. // this semaphore is introduced in the non-solution agent thread P(tobaccoSemaphore) P(mutex) if isPaper: isPaper = false V(matchSmokerSemaphore) else if isMatch: isMatch = false V(paperSmokerSemaphore) else: isTobacco = true V(mutex) The code for one smoker is presented below. The codes for other smokers are similar. P(matchSmokerSemaphore) makeCigarette() V(agentSemaphore) smoke() This pusher wakes up after one of the agent threads put tobacco on the table. If isPaper or isMatch is true, paper or match is on the table and has been "intercepted" (to be explained later) by one of the other two pushers. Therefore, this pusher can wake either the match smoker or the paper smoker up. If neither isPaper nor isMatch is true, paper or match is on the table (because there are two ingredients on the table and the first one is tobacco) but not yet "intercepted" by one of the other two pushers. In this case, this pusher set isTobacco to true so that any pusher who runs later will know that tobacco has been "intercepted". The mutex is used to ensure only one pusher enters the critical section at one time.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 7 of 8

The pushers can be seen as a processing unit which responds to the agent (or the available ingredients) by signaling the appropriate smoker. This is what we mean by "intercept" in the above paragraph. Moreover, isTobacco and tobaccoSemaphore are slightly different. isTobacco indicates whether tobacco has been "intercepted" while tobaccoSemaphore indicates whether tobacco is on the table. An ingredient can be on the table and not yet "intercepted" by the pushers. By using the pushers, we have separated the signal code from the agent. Compared to the agent in the trivial solution, which does both putting ingredients and signaling the smokers, this is more modular programming because the agent does one task and the pushers do one task. Later if the problem statement changes slightly, we only have to modify the pushers code. Thus, the restriction of not modifying the agent code is satisfied.

The Generalized Version


This version is suggested by Parnas where the agent does not wait for the smokers. That means the agent repeatedly puts the ingredients and there can be multiple instances of one ingredient on the table. How do we modify the solution to solve this generalized problem? We replace boolean variables by integers to keep track of the number of instances of each ingredient. nTobacco = 0 nPaper = 0 nMatch = 0 The pusher code is modified accordingly. P(tobaccoSemaphore) P(mutex) if nPaper > 0: nPaper--

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 8 of 8

V(matchSmokerSemaphore) else if isMatch > 0: nMatch-V(paperSmokerSemaphore) else: nTobacco++ V(mutex) Interestingly, in this case, the values of tobaccoSemaphore, paperSemaphore and matchSemaphore can go beyond 1, and that is absolutely fine. nTobacco is the number of "intercepted" but not yet combined with other ingredients while tobaccoSemaphore indicates the total number of instances of tobacco on the table. Each time a pusher enters the critical section, it looks for combinations of two ingredients. If there is such a combination, it signals the appropriate smoker. Otherwise, it just increases the number of "intercepted" instances of the corresponding ingredient.

References
1. Allen B. Downey (2005), The Little Book of Semaphores 2. Cigarette Smokers Problem (Wikipedia) [ Previous: 2.2.1 Sleeping Barber | Up: 2.2 Classical Problems | Next: 2.2.3 Unisex Bathroom ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_smokers.html

11/11/2007

Concurrency / Semaphore

Page 1 of 8

[ Previous: 2.2.2 Cigarette Smokers | Up: 2.2 Classical Problems | Next: 2.3 POSIX Library ]

2.2.3 Unisex Bathroom


Scenario
Have u visited the SOC1 building recently? Not much has changed in the physical structure since SOC relocated to COM1, but a refresher to everyone, there is only one female washroom on the 8th floor. So for all you guys, who get very annoyed that you have to go all the way down to the 3rd floor to use the common room, well SOC, gratefully, has come up with a solution; UNISEX common room!! Geek 1: Woah!!! Awesome, finally SoC is rocking it. Geek 2: Yeah lor, we become SCHOOL OF COOLS.

Readers are we getting ideas? Did it invoke thoughts of perhaps arranging a meeting with a special someone, when you are up all night coding? Or did your creative side get the better of you, and you commenced planning on an intricate, artistic and clever plan to get someone to notice you?

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 2 of 8

Which would eventually lead to something like this?

Take our advice, pause all those thoughts!! Geek 1: Wah wah, what and why?? Geek 2: Yeah, why? I was in such a nice place, a natural high. :( Yeah well, I was equally disappointed. We are not in the school of computing for no reason. The smart professors at SOC have devised a devious plan, such that people from the opposite sex can not use the washroom at same time. Geek 1 & Geek 2: (Wailing)!! So to put things in perspective we have the following situation: There cannot be men and women at the same time in the bathroom. There are n number of cubicles. As students of CS2106, you would realize that the problem could be sorted out, with the aid of semaphores. But potential problems that could arise are deadlock and starvation. Well we would deal with that in time. For now, lets get started by trying to prevent members of the opposite sex to mingle in the common room.

Solution
Semaphores Bathroom is the shared resource

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 3 of 8

Male and Females would acts as threads

Problems
Problems that arise as a result of the implementation of this protocol are: Deadlock This situation would arise, when all individuals who waiting to use the common room, never get to. This could arise as result of improper software implementation of the semaphores. Theoretically, since there are n(at least 1) cubicles, one person would get to use it, and deadlock would not occur. Denial/Starvation Since its a condition that we have one party being denied the access of resources. In this case the resource being the washroom. For example, we could have a situation, where there a whole line of women and just one man, waiting to use the common room. In this case, women would always be using up the n cubicles, and it would take a very long time before the gent would get an opportunity, because he would be unable to use it unless all n cubicles are free, due the no gender mixing policy.

Use of Semaphores
The solution involves the use of semaphores as mentioned earlier. Let us assume that there are 3 cubicles, i.e n = 3 femaleMultiplex this semaphore would be initialized to 3 (n). This would control the number of women in (making sure not more than 3). maleMultiplex similar function as for the femaleMultiplex, whoever applicable to males Empty is a mutex, it is 1 if room empty, 0 otherwise nextInLine is a mutex, if the sex of the person is different from the group/person inside it is 0, 1 otherwise maleSwitch LightSwitch() allows men to bar women from the room. When

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 4 of 8

the first male enters, the lightswitch locks empty, barring women; When the last male exits, it unlocks empty, allowing women to enter femaleSwitch LightSwitch(), Women do likewise using the same

Procedure #1 (Non-Solution)
When a person (assume female) arrives, and if there is no one in the bathroom, she would be allowed to enter. At this point femaleSwitch.lock(empty) would be called. This would bar men from accessing the bathroom. Then femaleMultiplex.wait() or P() would be called to decrease the semaphore of the females. (At wait(), it checks if there are vacancies, and only if there are any it proceeds.) If the bathroom is operating at maximum capacity then the female thread would wait until a vacancy opens up! Once the female (thread) is ready to leave, the thread would first increment the value in the femaleMultiplex semaphore, by calling

femaleMultiplex.signal() or V(). Now suppose, another female in queue wanted to enter, the thread too would follow the same steps as above. When the number of femaleMultiplex is 3 (n), that implies the bathroom is empty, at which point, femalSwitch.unlock(empty) is carried out. Every thread reads femaleSwtich.unlock(empty), however, it is unlocked only when the semaphore value is equal to maximum number of cubicles. (Although in theory, there is no operation to get the value of a semaphore, the library for semaphore implementation may have such a function. Even if it is not available, we can use a simple counter to keep track of the semaphore value manually.) This would notify that the bathroom is empty and would not bar men from entering. The pseudo code for the above, female version (similar version for males, using

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 5 of 8

male variables) is as follows: femaleSwitch.lock(empty) femaleMultiplex.wait() # bathroom code here femaleMultiplex.signal() femaleSwitch.unlock(empty) The problem with above solution is that the situation could go into a state of starvation. That is there could be a long line of females, and just one man, waiting. In which, case the male would not get a chance for a very long and unfair period of time, even though he might have been there before many females. This is because, in the above code we check the gender of the people in the bathroom, and allow a maximum number of individuals of the particular sex to go in until the bathroom is completely empty. So, a solution to the above problem is implementing nextInLine. This would check who is in queue next and would bar the thread if it is of the opposite nature the thread currently being processed. nextInLine is a mutex, if the sex of the person (the thread) is different from the group/person inside it is 0, 1 otherwise.

Procedure #2 (Solution)
We have a nextInLine mutex, which would check the incoming thread, i.e it would perform a nextInLine.wait() And bar a thread of the different nature to what is being currently processed. For example: suppose the room is currently with males, but next thread if female. Then nextInLine would block the female thread, and wait until all male threads have finished. Then the female thread would be allowed to enter. After the female thread gets through nextInLine.wait() (which means the

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 6 of 8

female enters the bathroom), the thread would call femaleSwitch.lock (empty), which bars males from entering. When exiting after using the bathroom (thread executes respective code), in the case where a female thread is waiting on existing male threads to finish utilizing the resource, the maleSwitch.unlock(empty) would check the male semaphore variable to ensure its equal to 3 (n number of cubicles), and signal that the bathroom is empty. Women would be allowed to enter to n capacity (as in procedure 1), until the next male arrives. Then the process repeats itself. So in this manner starvation would be avoided. This procedure may not be efficient. This system could perhaps be implemented in a system with several hundred threads. If there is a frequent change in the type (gender) of thread, then it would be blocked and hence barring additional threads. The pseudo code for the above procedure (for the male thread) is: nextInLine.wait() maleSwitch.lock(empty) nextInLine.signal() maleMultiplex.wait() # bathroom code here maleMultiplex.signal() maleSwitch.unlock(empty)

Procedure #3 (Efficiency vs Fairness)


The following solution is what we think can be an improvement in efficiency over the previous one. We could implement 2 queues, one for males and the other for females. Queuing numbers are given to each thread that arrives on a first come first serve basis. That is, in queue F (female), I could have numbers 1, 2, 4-10. And in queue

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 7 of 8

M (male), numbers 3, 8, 11. Suppose initially the bathroom is empty. So the first thread is a female, therefore, females would be allowed to enter to maximum capacity, in this case 3. Now when reading sequence numbers, since it is sequential (successor is +1 of the predecessor), there would be a hiccup after 2 in the female queue, as 3 is not there. This would mean that there are males waiting. However, instead of just letting 1 & 2 to enter, and then swapping to 3 (male queue) as would have been done in procedure 2, it is noted that a jump in sequence number has occurred, which is a sign to visit the male queue. Three (maximum capacity of bathroom) females would be allowed to enter, i.e. 1, 2 & 4. Then female threads are barred. The male thread 3 would be blocked. Once the threads 1, 2 & 4 have finished, thread 3 would be allowed to enter. Here again after 3, there is a jump, it is noted. Therefore, it would permit the next 2 in queue to enter, and then bar further male threads. Once again, we would swap to the female queue. The above procedure minimizes the number of swapping, as compared to procedure 2. It ensures that the bathroom always runs at full capacity, for either of the sex as long as there are people in the queue. Although threads are not entered in sequence order, it is obvious that the efficiency offsets the slight unfairness. The pseudo code is similar to procedure 2: // nextInLine checks for sequence number, in addition. If a jump is noted, it would adjust a variable, say unfair = (n - currentlyInside). Hence, that many from the current queue would be allowed, before being barred. After unfair amount have entered, the threads from the other queue would be allowed to enter.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 8 of 8

nextInLine.wait() // checks addition maleSwitch.lock(empty) nextInLine.signal()

for

sequence

number,

in

maleMultiplex.wait() # bathroom code here maleMultiplex.signal() maleSwitch.unlock(empty)

References
1. Allen B. Downey (2005), The Little Book of Semaphores 2. David J. Powers (2006), The Unisex Bathroom: Fairness versus Performance [ Previous: 2.2.2 Cigarette Smokers | Up: 2.2 Classical Problems | Next: 2.3 POSIX Library ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/implementation_bathroom.html

11/11/2007

Concurrency / Semaphore

Page 1 of 1

[ Previous: 2.2.3 Unisex Bathroom | Up: 2. Implementation | Next: 2.3.1 POSIX Mutexes ]

2.3 POSIX Library


POSIX or "Portable Operating System Interface" is the collective name of a family of related standards specified by the IEEE to define the application programming interface (API) for software compatible with variants of the Unix operating system. (Wikipedia) 2.3.1 POSIX Mutexes 2.3.2 POSIX Semaphores

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix.html

11/11/2007

Concurrency / Semaphore

Page 1 of 6

[ Previous: 2.2.3 Unisex Bathroom | Up: 2.3 POSIX Library | Next: 2.3.2 POSIX Semaphores ]

2.3.1 POSIX Mutexes


The following declarations show the POSIX API for threads involving mutex: #include <pthread.h> - the header file containing the relevant functions int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr) used to initialize the mutex int pthread_mutex_lock(pthread_mutex_t *mutex) used to lock a mutex so only one thread has exclusive access int pthread_mutex_unlock(pthread_mutex_ t *mutex) used to unlock a mutex so that other threads can access the mutex int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) makes the current thread wait until the specified condition is true int pthread_cond_signal(pthread_cond_t *cond) signals a waiting a thread when the specified condition becomes false Heres a program which illustrates the simple use of the pthread_cond_wait system call mentioned above There are two threads created here which uses two functions, functionAdd1 and functionAdd2 and three constant values DONE=10, STOP1=2 and STOP2=4 which is used for checking conditions. A variable sum is shared by both the threads. The functionAdd1 increments and prints the value of sum until the value of sum is equal to 2 and then it is made to wait by the pthread_cond_wait

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_mutexes.html

11/11/2007

Concurrency / Semaphore

Page 2 of 6

system call. Now functionAdd2 is given the access to the variable sum and it increments and prints the value of sum until the condition inside it becomes false, after which it signals functionAdd1 to proceed. Finally the program ends when the sum value is greater than or equal to 10. #include <stdio.h> #include <stdlib.h> #include <pthread.h> pthread_mutex_t sum_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t PTHREAD_MUTEX_INITIALIZER; condition_mutex =

pthread_cond_t condition_cond = PTHREAD_COND_INITIALIZER; void *functionAdd1(); void *functionAdd2(); int sum = 0; #define DONE 10 #define STOP1 2 #define STOP2 4 int count=0; int main() { pthread_t thread1, thread2; pthread_create(&thread1, NULL, &functionAdd1, NULL); pthread_create(&thread2, NULL, &functionAdd2, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); exit(0); } void *functionAdd1() { for(;;) { pthread_mutex_lock(&condition_mutex); while (sum >= STOP1 && sum <=STOP2){

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_mutexes.html

11/11/2007

Concurrency / Semaphore

Page 3 of 6

pthread_cond_wait(&condition_cond, &condition_mutex); } pthread_mutex_unlock(&condition_mutex); pthread_mutex_lock(&sum_mutex); sum++; printf("Sum value functionAdd1: %d\n",sum); pthread_mutex_unlock(&sum_mutex); if (sum >= DONE) return (NULL); } } void *functionAdd2() { for(;;) { pthread_mutex_lock(&condition_mutex); if(sum < STOP1 || sum > STOP2) { pthread_cond_signal(&condition_cond); } pthread_mutex_unlock(&condition_mutex); pthread_mutex_lock(&sum_mutex); sum++; printf("Sum value functionAdd2: %d\n",sum); pthread_mutex_unlock(&sum_mutex); if (sum >=DONE) return (NULL); } } Let us try to analyze the two functions, functionAdd1 and functionAdd2. functionAdd1 pthread_mutex_lock(&condition_mutex); while (sum >= STOP1 && sum <=STOP2){ pthread_cond_wait(&condition_cond, &condition_mutex); } pthread_mutex_unlock(&condition_mutex); These lines form the conditional part of the function.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_mutexes.html

11/11/2007

Concurrency / Semaphore

Page 4 of 6

First the lock mechanism of the mutex is called to provide the thread 1 with exclusive access of the conditional mutex. Then the value of sum is checked to see if its value is >=2 and <=4. As long as the condition is false thread 1 has control over the shared variable sum and the following lines are executed to increase the value of sum by 1 and print it out. pthread_mutex_lock(&sum_mutex); sum++; printf("Sum value functionAdd2: %d\n",sum); pthread_mutex_unlock(&sum_mutex); The incrementing of sum is done within the lock and unlock mechanisms. The pthread_mutex_lock(&sum_mutex) restricts access to any other function which wants to use the variable sum when functionAdd1 is using it. The pthread_mutex_unlock(&sum_mutex) removes the restriction imposed earlier so that other functions can also now access the variable sum. When the condition becomes true, then thread1 is made to wait and thread2 gains access to the variable sum. functionAdd2 pthread_mutex_lock(&sum_mutex); sum++; printf("Sum value functionAdd2: %d\n",sum); pthread_mutex_unlock(&sum_mutex); When thread2 gains access to the variable sum the above lines of code are first executed. The value of sum is incremented by one and printed out and do note that this is also done within the lock and unlock mechanisms. After the incrementing part is done once, the conditional mutex of the function is executed now.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_mutexes.html

11/11/2007

Concurrency / Semaphore

Page 5 of 6

pthread_mutex_lock(&condition_mutex); if(sum < STOP1 || sum > STOP2) { pthread_cond_signal(&condition_cond); } pthread_mutex_unlock(&condition_mutex); These lines above check to see if the value of sum is either less than 2 or greater than 4. If the condition is false then the function continues to increment the variable sum and print it out. When the condition becomes true, thread2 signals thread1 using the system call pthread_cond_signal(&condition_cond) and now thread1 gains back access to variable sum. Both the threads stop functioning when the value of sum is >= 10. The output: Sum Sum Sum Sum Sum Sum Sum Sum Sum Sum Sum value value value value value value value value value value value functionAdd1: functionAdd1: functionAdd2: functionAdd2: functionAdd2: functionAdd2: functionAdd1: functionAdd1: functionAdd1: functionAdd1: functionAdd2: 1 2 3 4 5 6 7 8 9 10 11

From the output we can see that the functionAdd1 was made to wait when the value of sum was between 2 and 4 and functionAdd2 increments the value of sum and prints it out during that time. NOTE: This program was mainly used to explain the working of the pthread_cond_wait system call as it might give rise to race conditions because the variable sum used in the while loop can't be locked without causing a deadlock situation.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_mutexes.html

11/11/2007

Concurrency / Semaphore

Page 6 of 6

[ Previous: 2.2.3 Unisex Bathroom | Up: 2.3 POSIX Library | Next: 2.3.2 POSIX Semaphores ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_mutexes.html

11/11/2007

Concurrency / Semaphore

Page 1 of 7

[ Previous: 2.3.1 POSIX Mutexes | Up: 2.3 POSIX Library | Next: 2.4 Case Study on Prex Semaphore ]

2.3.2 POSIX Semaphores


The POSIX semaphore types and functions can be found in <semaphore.h>.

Creating a Semaphore
int sem_init(sem_t *sem, int pshared, unsigned int value); Initializes an unnamed semaphore pointed to by sem to value. pshared indicates whether the semaphore is shared among processes (if pshared is zero, the semaphores is not shared; otherwise it is shared). Returns 0 on success and -1 on error Example sem_t sem; sem_init(&sem, 0, 1); sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); Initializes the named name semaphore to value. oflag is defined by OR'ing two values: O_CREAT (to create a semaphore if it does not exist) and O_EXCL (to return an error if the semaphore already exists). mode specifies the permission of the semaphore. Returns the address of the semaphore on success, and SEM_FAILED on

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 2 of 7

error. Example // the absolute path to the semaphore const char sem_name[] = "/tmp/sem"; // 0644 is the permission of the semaphore sem_t *sem = sem_open(sem_name, O_CREAT, 0644, 1);

Waiting on a Semaphore
int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); If the value of the semaphore pointed to by sem is greater than 0, sem_wait () decreases it by 1 and returns immediately. Otherwise, the process is blocked. sem_trywait() is similar to sem_wait() except that if the semaphore value is not greater than 0, it returns an error. sem_timedwait() specifies a time limit pointed to by abs_timeout. If the decrement cannot proceed and time limit expires, the function returns an error. All these functions return 0 on success and -1 on error. Example sem_t sem; ... if (sem_wait(&sem) == 0) { // do critical stuff }

Incrementing a Semaphore
int sem_post(sem_t *sem); Increments the value of the semaphore pointed to by sem. If the value becomes greater than 0, a process may be unblocked.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 3 of 7

Returns 0 on success and -1 on error. Example sem_t sem; ... // do critical stuff sem_post(&sem);

Getting the Value of a Semaphore


int sem_getvalue(sem_t *sem, int *sval); Gets the value of the semaphore pointed to by sem and places it in the location pointed to by sval. Returns 0 on success and -1 on error. Example sem_t sem; ... int value; sem_getvalue(&sem, &value);

Destroying a Semaphore
int sem_destroy(sem_t *sem); Destroys the unnamed semaphore pointed to by sem (only used for semaphores initialized by sem_init()). If there are processes waiting on this semaphore or the semaphore has already been destroyed, the function may produce undefined behaviour. Returns 0 on success and -1 on error. Example sem_t sem; ... sem_destroy(&sem);

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 4 of 7

int sem_close(sem_t *sem); Disassociates the named semaphore pointed by sem from the process (only used for semaphores created by sem_open()). The state of the semaphore is preserved after it is closed. If the semaphore is reopened, it returns to the state when it was closed. Returns 0 on success and -1 on error. Example const char sem_name[] = "/tmp/sem"; sem_t *sem = sem_open(sem_name, O_CREAT, 0644, 1); ... sem_close(sem); int sem_unlink(const char *name); Removes the named name semaphore. This is used after all the processes have closed the semaphore by calling sem_close(). Returns 0 on success and -1 on error. Example const char sem_name[] = "/tmp/sem"; sem_t *sem = sem_open(sem_name, O_CREAT, 0644, 1); ... sem_close(sem); sem_unlink(sem_name);

Example
In the following program, we have 3 threads. Each thread needs to enter the critical section 3 times, where it sleeps for a random amount of time. We initialize the semaphore to 2 so that at most 2 threads can be in the critical section at the same time. We can see in the output that this is indeed the case. #include <pthread.h> #include <semaphore.h>

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 5 of 7

#include #include #include #include

<stdio.h> <stdlib.h> <sys/types.h> <unistd.h>

void *doSomething1(); void *doSomething2(); void *doSomething3(); sem_t sem; int main() { // initialize semaphore to 2 sem_init(&sem, 1, 2); pthread_t thread1, thread2, thread3; pthread_create(&thread1, NULL, &doSomething1, NULL); pthread_create(&thread2, NULL, &doSomething2, NULL); pthread_create(&thread3, NULL, &doSomething3, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_join(thread3, NULL); return 0; } void doSomething(char c) { int i, time; for (i = 0; i < 3; i++) { // P operation if (sem_wait(&sem) == 0) { // seconds) time = (int) ((double) rand() / RAND_MAX * 30); printf("Thread %c enters and sleeps for %d seconds...\n", c, time); sleep(time); printf("Thread section\n", c); // V operation %c leaves the critical generate random amount of time (< 30

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 6 of 7

sem_post(&sem); } } } void *doSomething1() { // thread A doSomething('A'); return 0; } void *doSomething2() { // thread B doSomething('B'); return 0; } void *doSomething3() { // thread C doSomething('C'); return 0; } Run the program: $ gcc -Wall -o sem sem.c -lpthread -lrt $ ./sem -lpthread and -lrt are options passed into the compiler to use pthreads and POSIX semaphores. Output Thread A enters and Thread B enters Thread B leaves Thread B enters Thread A leaves the Thread A enters and Thread B leaves Thread B enters sleeps for 25 seconds... and sleeps for 11 seconds... the critical section and sleeps for 23 seconds... critical section sleeps for 23 seconds... the critical section and sleeps for 27 seconds...

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 7 of 7

Thread A leaves the Thread A enters and Thread A leaves the Thread C enters and Thread B leaves Thread C leaves the Thread C enters and Thread C leaves the Thread C enters and Thread C leaves the

critical section sleeps for 5 seconds... critical section sleeps for 10 seconds... the critical section critical section sleeps for 23 seconds... critical section sleeps for 8 seconds... critical section

The indentation and use of colours are to make the output a bit easier to read. Two lines that have the same colour and indentation level indicate that they are the correspoding entry and exit to and from the critical section of one thread. With the use of a random amount of sleeping time inside the critical section, we can see the interleavings of the threads more clearly. We also notice that there are at most two threads in the critical section at the same time (i.e. at most two nested "enters..." with different colours). This is what we expect because the semaphore is initialized to 2. Through this example, we have shown how POSIX semaphores can be used. We also go through the output of the program and verify that the maximum number of threads that can be in the critical section at the same time is exactly the initial value of the semaphore.

References
1. Semaphores 2. Synchronizing Threads with POSIX Semaphores 3. Linux man pages 4. Semaphores, Message Queues and Shared Memory 5. IPC:Semaphores (Information on UNIX System V IPC Semaphores, another way to implement/use semaphores.) [ Previous: 2.3.1 POSIX Mutexes | Up: 2.3 POSIX Library | Next: 2.4 Case Study on Prex Semaphore ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/posix_semaphores.html

11/11/2007

Concurrency / Semaphore

Page 1 of 9

[ Previous: 2.3.2 POSIX Semaphores | Up: 2. Implementation | Next: 3. Advantages and Disadvantages ]

2.4 Case Study on Prex Semaphore


In this section, we study the implementation of semaphore in Prex, a portable real-time operating system for embedded systems. Prex provides fundamental features for synchronization with mutexes/semaphores, and at the same time, the source code is not too complicated to understand. In addition, the website provides a nice way to browse the source code. The data structure for semaphore: struct semaphore { int magic; /* Magic number */ task_t task; /* Owner task */ struct event event; /* Event */ u_int value; /* Current value */ }; The magic number is used to check whether an object is a semaphore. This is true only if the magic number is equal to the constant SEM_MAGIC. event is a data structure used to sleep or wake up a process. struct event { struct queue sleepq; /* Queue for waiting thread */ char *name; /* Pointer to event name string */ }; There is a queue to keep track of the processes waiting on a semaphore. We will see later that the order the processes are waken up is not necessarily FIFO.

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 2 of 9

The following operations on semaphore can be found in root/sync/sem.c.

Creating a Semaphore
__syscall int sem_init(sem_t *sem, u_int value) { struct semaphore *s, *sem_org; int err = 0; ... /* Disable synchronization */ sched_lock(); the thread switch to ensure

/* Copy the pointer from user to kernel space */ if (umem_copyin(sem, &sem_org, sizeof(sem_t))) { ... } /* An application can call sem_init() to reset the * value of existing semaphore. So, we have to check * the semaphore is already allocated. */ if (sem_valid(sem_org)) { /* Semaphore already exists. * We modify the value only if: * (i) the thread is the owner of the semaphore * or it has the right to access another thread's semaphore. * (ii) no process is waiting on the semaphore */ if (sem_org->task != cur_task() && !task_capable(CAP_SEMAPHORE)) err = EPERM; else if (event_waiting(&sem_org->event)) err = EBUSY; else s->value = value; } else { /* Create new semaphore. * * First we allocate memory block for kernel */ if ((s = kmem_alloc(sizeof(struct

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 3 of 9

semaphore))) == NULL) err = ENOSPC; else { /* Initialize the semaphore */ event_init(&s->event, "semaphore"); s->task = cur_task(); s->value = value; s->magic = SEM_MAGIC; /* Copy the pointer from kernel to user space */ if (umem_copyout(&s, sem, sizeof(sem_t))) { ... } } } /* Unlock the thread switch */ sched_unlock(); return err; } In the above code, we copy the pointer from user to kernel space and check whether the pointer points to a valid semaphore. If the pointer is valid, we modify the value of the semaphore only if we have the right to access the semaphore and no process is waiting on the semaphore. If the pointer is invalid, we create a new semaphore in the kernel space, initialize and copy it to the user space.

Destroying a Semaphore
__syscall int sem_destroy(sem_t *sem) { sem_t s; int err; sched_lock(); /* Copy the semaphore from user to kernel space * and check whether we have the right to access the semaphore

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 4 of 9

*/ if ((err = sem_copyin(sem, &s))) { ... } /* If there is a process waiting on the semaphore, * the method fails with error */ if (event_waiting(&s->event)) { sched_unlock(); return EBUSY; } /* Otherwise we destroy the semaphore */ s->magic = 0; kmem_free(s); sched_unlock(); return err; } If we have the right to access the semaphore and no process is waiting on the semaphore, we can destroy it. This is done by copying the semaphore to kernel space, setting the magic number to zero and finally free the memory block in kernel space associated with the semaphore.

Waiting on a Semaphore
__syscall int sem_wait(sem_t *sem, u_long timeout) { sem_t s; int err; sched_lock(); /* Copy the semaphore from user to kernel space * and check whether we have the right to access the semaphore */ if ((err = sem_copyin(sem, &s))) { ... }

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 5 of 9

while (s->value <= 0) { /* Please see the code of sched_tsleep() below */ err = sched_tsleep(&s->event, timeout); ... /* Unlock and the lock the thread switch and * process all all pending woken threads */ sched_unlock(); /* Lock the thread switch */ sched_lock(); } /* Finally the value of the semaphore is greater than 0 * and we can decrement it */ s->value--; sched_unlock(); return 0; }

int sched_tsleep(event_t event, u_long timeout) { thread_t th; int stat; ... /* Disable interrupts (temporarily) */ interrupt_save(&stat); interrupt_disable(); /* Put the current thread to the sleeping queue of the semaphore */ th = cur_thread; th->sleep_event = event; th->state |= TH_SLEEP; enqueue(&event->sleepq, &th->link); /* If timeout > 0, set the timer so that the sleep will be cancelled * after the time limit expires and the thread will be removed

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 6 of 9

* from the sleeping queue. This is similar to sem_timedwait of POSIX library */ if (timeout) timer_timeout(&th->timeout, sleep_timeout, th, timeout); /* Process all pending woken threads to prepare for * the following thread switch call */ wakeq_flush(); sched_switch(); /* Sleep here. Zzzz.. */ /* Restore interrupts */ interrupt_restore(stat); return th->sleep_result; } In summary, if a process waits on a semaphore and the value of the semaphore is less than or equal to zero, the thread goes to sleep and is put on the queue of the semaphore. The sleep requests are not interruptible, and thus, the interrupts are disabled temporarily while the thread is being put to sleep. When the value of the semaphore is finally greater than one, we can decrement the value and let the thread enter the critical section. __syscall int sem_trywait(sem_t *sem) This is similar to sem_wait except that if the value of the semaphore is not greater than zero, it returns an error and the calling thread does not go to sleep.

Incrementing a Semaphore
__syscall int sem_post(sem_t *sem) { sem_t s; int err; sched_lock();

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 7 of 9

/* Copy the semaphore from user to kernel space * and check whether we have the right to access the semaphore */ if ((err = sem_copyin(sem, &s))) { ... } ... s->value++; if (s->value > 0) /* Please see the code of sched_wakeone() below */ sched_wakeone(&s->event); sched_unlock(); return 0; }

thread_t sched_wakeone(event_t event) { queue_t head, q; thread_t top, th = NULL; ... /* Disable hardware interrupts */ irq_lock(); head = &event->sleepq; if (!queue_empty(head)) { q = queue_first(head); top = queue_entry(q, struct thread, link); /* Traverse the sleeping queue of the semaphore and * select the highest priority thread */ while (!queue_end(head, q)) { th = queue_entry(q, struct thread, link); if (th->prio < top->prio) top = th; q = queue_next(q); } /* Remove the selected thread from the

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 8 of 9

sleeping queue and * wake it up */ queue_remove(&top->link); enqueue(&wakeq, &top->link); timer_stop(&top->timeout); } /* Restore hardward interrupts */ irq_unlock(); return th; } We increment the value of the semaphore. If it is greater than zero, the highest priority thread among the sleeping threads is waken up. It is interesting to note that although we always add a thread to the end of the sleeping queue, we do not dequeue the threads in FIFO order. Priority is used instead.

Getting the Value of a Semaphore


__syscall int sem_getvalue(sem_t *sem, u_int *value) { sem_t s; int err; sched_lock(); /* Copy the semaphore from user to kernel space * and check whether we have the right to access the semaphore */ if ((err = sem_copyin(sem, &s))) { ... } /* Copy the value of the semaphore from kernel to user space */ if (umem_copyout(&s->value, value, sizeof(int))) err = EFAULT; sched_unlock(); return err;

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 9 of 9

} We copy the semaphore from user to kernel space, get the value of the semaphore and copy the value back to user space to return.

Summary
We have gone through the code related to semaphore in the Prex operating system. Through the case study, we have a better understanding of the implementation of sempahore in an operating system, e.g. the distinction between user and kernel space, the need to disable interrupts temporarily, the use of sleeping queue and the choice to pick the highest priority thread to wake up. Interested readers can also have a look at the implementation of mutex and condition variable in this operating system.

References
1. Browse Prex Source [ Previous: 2.3.2 POSIX Semaphores | Up: 2. Implementation | Next: 3. Advantages and Disadvantages ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/case_study.html

11/11/2007

Concurrency / Semaphore

Page 1 of 2

[ Previous: 2.4 Case Study on Prex Semaphore | Up: Site Map | Next: 4. Alternatives ]

3. Advantages and Disadvantages


Advantages
In semaphores there is no spinning, hence no waste of resources due to no busy waiting. That is because threads intending to access the critical section are queued. And could access the priority section when the are de-queued, which is done by the semaphore implementation itself, hence, unnecessary CPU time is not spent on checking if a condition is satisfied to allow the thread to access the critical section. Semaphores permit more than one thread to access the critical section, in contrast to alternative solution of synchronization like monitors, which follow the mutual exclusion principle strictly. Hence, semaphores allow flexible resource management. Finally, semaphores are machine independent, as they are implemented in the machine independent code of the microkernel services.

Disadvantages
Problem 1: Programming using Semaphores makes life harder as utmost care must be taken to ensure Ps and Vs are inserted correspondingly and in the correct order so that mutual exclusion and deadlocks are prevented. In addition, it is difficult to produce a structured layout for a program as the Ps and Vs are scattered all over the place. So the modularity is lost. Semaphores are quite

http://www.comp.nus.edu.sg/~phanquyt/cs2106/advantages_disadvantages.html

11/11/2007

Concurrency / Semaphore

Page 2 of 2

impractical when it comes to large scale use. Problem 2: Semaphores involve a queue in its implementation. For a FIFO queue, there is a high probability for a priority inversion to take place wherein a high priority process which came a bit later might just have to wait when a low priority one is in the critical section. For example, consider a case when a new smoker joins and is desperate to smoke. What if the agent who handles the distribution of the ingredients follows a FIFO queue (wherein the desperate smoker is last according to FIFO) and chooses the ingredients apt for another smoker who would rather wait some more time for a next puff?

References
1. Generic semaphore for concurrent access by multiple operating systems [ Previous: 2.4 Case Study on Prex Semaphore | Up: Site Map | Next: 4. Alternatives ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/advantages_disadvantages.html

11/11/2007

Concurrency / Semaphore

Page 1 of 5

[ Previous: 3. Advantages and Disadvantages | Up: Site Map | Next: 5. Conclusion ]

4. Alternatives
Monitors
Well first and foremost its a language based mechanism, where in semaphores are lowlevel synchronization primitives. Monitors were also conceived to address the short comings of semaphores, in chief, the scattered nature of semaphores and the low level implementation, i.e. the convenience of forgetting P or a V, and to address the former reason in a semaphore based implementation, if you want to make changes, it is absolutely compulsory to find all places in the code, and amend accordingly. Which might take a very long time, and could be error prone for obvious reasons. While semaphores is a special type of counter, monitors are a shared object. A monitor might have many entry points (condition variable), however, only one process can used to enter the monitor at any time. Hence, it implements mutual exclusion in contrast to semaphores (if init n > 1). A monitor consists of: Procedures that allow interaction with shared resources. A mutex lock, i.e. to allow only one process to utilize it The resources variables Invariant to avoid race conditions Suppose a procedure ought to be implemented if a certain condition is met, i.e a process can suspend itself and release the monitor, this is achieved with condition variable. Another process wanting to access that function/procedure would be queued for that particular procedure. Continuing on with our favourite Hollywood actress Angelina Jolie, we would show how, her bank account could be implemented. monitor Jolieaccount { int balance := 0

http://www.comp.nus.edu.sg/~phanquyt/cs2106/alternatives.html

11/11/2007

Concurrency / Semaphore

Page 2 of 5

function withdraw(int amount) { if amount < 0 then error "Amount may not be negative" else if balance < amount then error "Insufficient funds" else balance := balance - amount } function deposit(int amount) { if amount < 0 then error "Amount may not be negative" else balance := balance + amount } } Notice that in the functions, P and V arent explicitly stated, this is because the compiler inserts it in. In the code above the invariant balance, specifies that the balance, reflect past transactions. Since the monitor has a lock associated only Jolie or Pitt could access the account in a given time. So no worries to their millions. Suppose Jolie decided for every 1000$ she deposited, she wanted a $100 to be given to a charity, all that has to be done is to come and insert the relevant code in the function deposit. However, in the semaphore based implementation, each and every place where the critical section is, we would have to add it. So as you can see it would be very cumbersome. Lets look at the problem with the implementation of condition variables. class Account extends Object { private double balance = 0; private CondVar OKtoWithdraw = new CondVar; public synchronized void deposit(double amount) { balance = balance + amount; notifyAll(OKtoWithdraw); } public synchronized void withdraw(double amount) { while (amount > balance) wait(OKtoWithdraw); balance = balance - amount; } }

wait(CondVar cond) { put the calling thread on the wait set of cond; release lock; Thread.currentThread.suspend();

http://www.comp.nus.edu.sg/~phanquyt/cs2106/alternatives.html

11/11/2007

Concurrency / Semaphore

Page 3 of 5

acquire lock; } notifyAll(CondVar cond) { forall t in wait set of cond t.resume() } There is one condition variables declared here, OktoWithdraw. In this monitor, the deposit method/function/procedure after updating the balance, calls notifyAll(OktoWithdraw), in which, it would notify all threads in queue and they would resume activity. The rationale behind this is we may have some thread not able to withdraw the money because the amount is greater than the balance. After a deposit, we would want that thread to resume activity and check whether the balance now contains enough money for withdrawal. When withdrawing, the procedure checks if the amount requested for withdrawal is greater than the balance and then calls the wait(OKtoWithdraw). In the wait method, the calling thread is enqueued in the queue associated with the condition variable OktoWithdraw. The monitor lock is released and the calling thread suspends operation. The other threads which were waiting earlier for the monitor lock now try to acquire the lock again. If the thread requests more money than the balance, it is made to wait (again) and the lock is passed on to the next thread trying to access it. When the condition finally becomes true, i.e. the amount of withdrawal is less than the balance (maybe after a deposit), the line "balance = balance - amount;" is carried out to update the balance.

Java Synchronization (Monitors)


Java uses the synchronized keyword to block a statement or a chunk of code so that only one thread can access it at a time. The synchronized keyword acts like a lock and it blocks all other threads from entering into that section of code, when it is being used by a thread. This is the Java way of implementing monitors. In addition to placing the synchronized keyword before a chunk of code, it may also be used in conjunction with wait,notify or notifyAll statements, as these allow threads to wait when another thread wants to take over the synchronized code. Lets understand better with the following code: Hope you all remember the scenario where Angelina Jolie and Brad Pitt tried accessing Jolies account at the same time and was left with $0! Well so the banks nowadays

http://www.comp.nus.edu.sg/~phanquyt/cs2106/alternatives.html

11/11/2007

Concurrency / Semaphore

Page 4 of 5

prevented that from happening by using a similar code like: /** * Make a deposit to this account * @param depositAmount amount to deposit in pennies */ public synchronized void deposit( long depositAmount ) throws BankingException { if ( depositAmount < 0 ) { throw new BankingException( "Negative deposits should be handled as corrections or withdrawals" ); } this.balance += depositAmount; } /** * Make a withdrawal from this account * @param withDrawAmount amount to withdraw in pennies */ public synchronized void withdraw( long withdrawAmount ) throws BankingException { if ( withdrawAmount < 0 ) { throw new BankingException( "Negative withrawals should be handled as corrections or deposits" ); } if ( balance < withdrawAmount ) { throw new BankingException( "Insufficient funds" ); } this.balance -= withDrawAmount; } The situation now becomes as we would prefer: Angelina Jolie Brad Pitt

Load currentBalance=1 million Deposit newAmount=1 million (new balance 2 million) Update currentBalance=2 million

Load currentBalance=2 million

http://www.comp.nus.edu.sg/~phanquyt/cs2106/alternatives.html

11/11/2007

Concurrency / Semaphore

Page 5 of 5

Withdraw newAmount=1 million (new balance 1 million) Update currentBalance=1 million

References
1. Multithreaded Programming with ThreadMentor: A Tutorial 2. Semaphores & Monitors 3. Monitor (Wikipedia) 4. Semaphore vs Monitor 5. Empirical Evaluation of a UML Sequence Diagram with Adornments to Support Understanding of Thread Interactions [ Previous: 3. Advantages and Disadvantages | Up: Site Map | Next: 5. Conclusion ]

http://www.comp.nus.edu.sg/~phanquyt/cs2106/alternatives.html

11/11/2007

Concurrency / Semaphore

Page 1 of 1

[ Previous: 4. Alternatives | Up: Site Map ]

5. Conclusion
Now our journey with semaphores, unfortunately comes to an end. We looked at how semaphores are implemented in operating systems, how they are used in various situations and, in addition, other solutions such as monitors and condition variables to address the concurrency issue. The machine independent implementation of semaphore and the POSIX library makes it a versatile tool for many platforms. On that note, we hope that you enjoyed this, as we did, exploring this crazy world of synchronization and concurrency. But please take note, this is just the tip of the iceberg, so be adventurous and explore the big bad world, if you are really interested follow the module Parallel and Concurrent Programming =)

http://www.comp.nus.edu.sg/~phanquyt/cs2106/conclusion.html

11/11/2007

Das könnte Ihnen auch gefallen