Categories
PHP Programming

PHP Dark Arts: Semaphores

Note:  The full example code can be downloaded here.

Dijkstra contributed many important things to computer science, and among them was the semaphore.  A semaphore is a protected variable or abstract data type that is used for controlling access to some resource.  Semaphores can be used to control access to almost anything.  For example, lets say that I have multiple instances of a program running and this program counts how many rows are in table ‘X’ every 15 seconds.  We don’t want the program instances clobbering the database all at once, so we use a semaphore to control the access (I realize this is a silly idea, but it’s to illustrate a point).  Before a program instance can access the database, it must first acquire the semaphore.  Once acquired, it may run it’s query, and then release the semaphore so another program instance can use it.  In this way, access to the database is strictly controlled.

Semaphores have drawbacks of course.  The most obvious is that it’s easy to enter a state of deadlock.  Let’s say in the previous example, one program instance never releases the semaphore.  What happens then?  Everything comes to a screeching halt.  That’s why it’s important to be extremely careful when using a semaphore.  If you acquire it, be sure to release it.

PHP’s Semaphore Implementation

Using semaphores in PHP is actually very straight forward.  There are only 4 semaphore functions:

  • sem_acquire() – Attempt to acquire control of a semaphore.
  • sem_get() – Creates (or gets if already present) a semaphore.
  • sem_release() – Releases the a semaphore if it is already acquired.
  • sem_remove() – Removes (deletes) a semaphore.

So how do they all work together?  First, you call sem_get() to fetch the identifier for the semaphore.  After that, one of your processes will call sem_acquire() to try and acquire the semaphore.  If it’s currently unavailable, sem_acquire() will block until the semaphore is released by another process.  Once the semaphore is acquired, you may access the resource that you are controlling with it.  After you are done with the resource, call sem_release() so that another process can acquire the semaphore.  When all is said and done, and you’ve made sure that none of your processes require the semaphore anymore, you can call sem_remove() to remove the semaphore completely.

Getting and Removing

The first step in using a semaphore is calling sem_get().

1
2
3
4
5
6
$key = 123321;
$maxAcquire = 1;
$permissions =0666;
$autoRelease = 1;
 
$semaphore = sem_get($key, $maxAcquire, $permissions, $autoRelease);

The parameters for this function are pretty straight forward, but here is a break down just in case.

  • $key – A unique integer so that the semaphore is easily identifiable.
  • $maxAcquire – How many process can acquire the semaphore at once?
  • $permissionsUnix style permissions on the semaphore.
  • $autoRelease – Do you want the semaphore to release automatically if the request shuts down?

Each process needs to call sem_get() using the same parameters, otherwise it will get a different semaphore.  And if you want to remove your semaphore?  Just make sure you have a valid semaphore resource and then call sem_remove().

1
2
3
4
5
if(sem_remove($semaphore)) {
     echo "Semaphore removed. \n";
} else {
     echo "Failed to remove semaphore. \n";
}

Acquiring and Releasing

Once you’ve created (or retrieved) a semaphore, you can then start using it by calling sem_acquire().  Both sem_acquire() and sem_release() only take one parameter: a semaphore resource.

1
2
3
4
$semaphore = sem_get($key, $maxAcquire, $permissions, $autoRelease);
sem_acquire($semaphore);  //blocking
echo "hello world!";
sem_release($semaphore);

Now that you understand how to use PHP’s implementation of semaphores, we should try them out fully.

Controlling Access to Standard Input (STDIN) Using Semaphores

When learning about semaphores, a classic example is to control access to standard input.  There honestly isn’t much to the example, so I’ll just give you a quick breakdown of what happens and then let the code do the talking.

  1. Set the semaphore properties.
  2. Get the semaphore.
  3. Start a loop, and try to acquire the semaphore.
  4. Once acquired, access standard input.
  5. Once input is received store it, then release the semaphore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Semaphore properties
$key = 123456;
$max = 1;
$permissions = 0666;
$autoRelease = 1;
 
//Open a new or get an existing semaphore
$semaphore = sem_get($key, $max, $permissions, $autoRelease);
if(!$semaphore) {
     echo "Failed on sem_get().\n";
     exit;
}
 
//Try to aquire the semaphore.
for($i = 0; $i < 2; $i++) {
     echo "\nAttempting to acquire semaphore...\n";
     sem_acquire($semaphore);
 
     echo "Aquired.\n";
     echo "Enter some text: ";
     $handler = fopen("php://stdin", "r");
     $text = fgets($handler);
 
     fclose($handler);
     sem_release($semaphore);
 
     echo "Got: $text \n";
}

Running the Example


First, you should download the example here.  After that open up two terminal windows and run the following in each:

{code} php semaphore.php {/code}

Did you like this article?  You’ll probably like these other PHP Dark Arts articles too.

By Jack Slingerland

Founder of Kernl.us. Working and living in Raleigh, NC. I manage a team of software engineers and work in Python, Django, TypeScript, Node.js, React+Redux, Angular, and PHP. I enjoy hanging out with my wife and son, lifting weights, and advancing Kernl.us in my free time.

13 replies on “PHP Dark Arts: Semaphores”

Have you got much experience of how often php semaphores will deadlock?

Im thinking of using them to delegate for a job queue… but need 100% uptime. If a process aquires the semaphore and then crashes without releasing, is it possible to have a second process somehow release the semaphore?

I don’t believe “crashing without releasing” is itself “deadlock”.

Deadlock happens when two processes are waiting on each other – process A has acquired semaphore X, and while that is processing, process B acquires semaphore Y. The deadlock occurs if process A then tries to acquire semaphore Y and process B tries to acquire semaphore X. The two processes are then waiting for each other, and neither can continue until the other continues, neither of which can.

So in theory it can happen, but in practice it can be avoided in most designs.

If your multiple processes are sharing a single queue and acquiring a single semaphore to do so, then you should avoid deadlock.

You are probably correct about “crashing without releasing” not being a proper deadlock. A proper deadlock would have had each process waiting for a resource that the other has (so maybe using 2 semaphores instead). However, the result is pretty much the same. Either way, if one plans to using semaphores, it’s worth reading up on the 4 necessary conditions for a deadlock to occur (http://en.wikipedia.org/wiki/Deadlock#Necessary_conditions).

In general, semaphores are extremely stable, and they won’t just deadlock on their own. It only happens if there is an error in your design. However, if the process does terminate prematurely without releasing the semaphore, the $auto_release = 1 parameter for sem_get() should allow it to be released automatically. Hope this helps!

Comments are closed.