|
OSBOS Annotated BeBook |
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.
Reader Comments
No Comments for this Page.
To add a comment, you must be logged in to the beunited.org portal.