The Kernel Kit: Semaphore Examples

The Kernel Kit Table of Contents | The Kernel Kit Index

# Semaphore Examples

The following sections provides examples of typical semaphore use. For the full story on semaphores, see Semaphores.

## Semaphore Example 1: Locking

The most typical use of a semaphore is to protect a chunk of code that can only be executed by one thread at a time. The semaphore acts as a lock; `acquire_sem()` locks the code, `release_sem()` releases it. Semaphores that are used as locks are (almost always) created with a thread count of 1.

As a simple example, let's say you keep track of a maximum value like this:

       /* max_val is a global. */
       uint32 max_val = 0;
       ...

       /* bump_max() resets the max value, if necessary. */
       void bump_max(uint32 new_value)
       {
          if (new_value > max_value)
             max_value = new_value;
       }

bump_max() isn't thread safe; there's a race condition between the comparison and the assignment. So we protect it with a semaphore:

       sem_id max_sem;
       uint32 max_val = 0;

       ...
       /* Initialize the semaphore during a setup routine. */
       status_t init()
       {
          if ((max_sem = create_sem(1, "max_sem")) < B_NO_ERROR)
             return B_ERROR;
          ...
       }
       void bump_max(uint32 new_value)
       {
          if (acquire_sem(max_sem) != B_NO_ERROR)
             return;
          if (new_value > max_value)
             max_value = new_value;
          release_sem();
       }

## Semaphore Example 2: Benaphores

A "benaphore" is a combination of an atomic variable and a semaphore that can improve locking efficiency. If you're using a semaphore as shown in the previous example, you should consider using a benaphore instead (if you can).

Here's the example re-written to use a benaphore:

       sem_id max_sem;
       uint32 max_val = 0;
       int32 ben_val = 0;

       status_t init()
       {
          /* This time we initialized the semaphore to 0. */
          if ((max_sem = create_sem(0, "max_sem")) < B_NO_ERROR)
             return B_ERROR;
          ...
       }
       void bump_max(uint32 new_value)
       {
          int32 previous = atomic_add(&ben_val, 1);
          if (previous >= 1)
             if (acquire_sem(max_sem) != B_NO_ERROR)
                goto get_out;

          if (new_value > max_value)
             max_value = new_value;

       get_out:
          previous = atomic_add(&ben_val, -1);
          if (previous > 1)
             release_sem(max_sem);
       }

The point, here, is that `acquire_sem()` is called only if it's known (by checking the previous value of `ben_val`) that some other thread is in the middle of the critical section. On the releasing end, the `release_sem()` is called only if some other thread has since entered the function (and is now blocked in the `acquire_sem()` call). An important point, here, is that the semaphore is initialized to 0.

## Semaphore Example 3: Imposing an Execution Order

Semaphores can also be used to coordinate threads that are performing separate operations, but that need to perform these operations in a particular order. In the following example, we have a global buffer that's accessed through separate reading and writing functions. Furthermore, we want writes and reads to alternate, with a write going first.

We can lock the entire buffer with a single semaphore, but to enforce alternation we need two semaphores:

       sem_id write_sem, read_sem;
       char buffer[1024];

       /* Initialize the semaphores */
       status_t init()
       {
          if ((write_sem = create_sem(1, "write")) < B_NO_ERROR) {
             return;
          if ((read_sem = create_sem(0, "read")) < B_NO_ERROR) {
             delete_sem(write_sem);
             return;
          }
       }

       status_t write_buffer(const char *src)
       {
          if (acquire_sem(write_sem) != B_NO_ERROR)
             return B_ERROR;

          strncpy(buffer, src, 1024);

          release_sem(read_sem);
       }

       status_t read_buffer(char *dest, size_t len)
       {
          if (acquire_sem(read_sem) != B_NO_ERROR)
             return B_ERROR;

          strncpy(dest, buffer, len);

          release_sem(write_sem);
       }

The initial thread counts ensure that the buffer will be written to before it's read: If a reader arrives before a writer, the reader will block until the writer releases the `read_sem` semaphore.

The Kernel Kit Table of Contents | The Kernel Kit Index

The Be Book,
...in lovely HTML...
for BeOS Release 5.

Copyright © 2000 Be, Inc. All rights reserved..