Semaphores
Semaphores are used for synchronization of processes and threads. Semaphores are clubbed with message queues and shared memory under the Interprocess Communication (IPC) facilities in Unix-like systems such as Linux. There are two varieties of semaphores, the traditional System V semaphores and the newer POSIX semaphores. In this post we will look at the POSIX semaphores.
Table of Contents
1.0 POSIX Semaphores
POSIX semaphore calls are much simpler than the System V semaphore calls. However, System V semaphores are more widely available, particularly on older Unix-like systems. POSIX semaphores have been available on Linux systems post version 2.6 that use glibc.
There are two types of POSIX semaphores – named and unnamed. As the terminology suggests, named semaphores have a name, which is of the format /somename. The first character is a forward slash, followed by one or more characters, none of which is a slash. We will first look at the named semaphores and then the unnamed ones.
Programs using POSIX semaphores need to be linked with the pthread library.
2.0 POSIX Named Semaphore calls
2.1 sem_open
#include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> sem_t *sem_open (const char *name, int oflag); sem_t *sem_open (const char *name, int oflag, mode_t mode, unsigned int value);
sem_open is the call to get started for a semaphore. sem_open opens an existing semaphore or creates a new semaphore and opens it for further operations. The first parameter, name, is the name of the semaphore, coined as described earlier. The oflag can have O_CREAT, in which case, the semaphore is created if it does not already exist. If both O_CREAT and O_EXCL are specified, the call gives an error, if the semaphore with the specified name already exists. If the oflags parameter has O_CREAT set, the second form of sem_open has to be used and two additional parameters, mode and value have to be specified. The mode parameter specifies the permissions for the semaphore, which are masked with the umask for the process, similar to the mode in the open system call for files. The last parameter, value is the initial value for the semaphore. If O_CREAT is specified in oflag and the semaphore already exists, both the mode and value parameters are ignored.
sem_open returns a pointer to the semaphore on success. This pointer has to be used in the subsequent calls for the semaphore. If the call fails, sem_open returns SEM_FAILED and errno is set the appropriate error.
Under Linux, POSIX semaphores are created under the /dev/shm directory. The semaphores are named with a prefix, sem.
followed by the name passed in the sem_open call.
2.2 sem_post
#include <semaphore.h> int sem_post (sem_t *sem);
sem_post increments the semaphore. It provides the V operation for the semaphore. It returns 0 on success and -1 on error.
2.3 sem_wait
#include <semaphore.h> int sem_wait (sem_t *sem);
sem_wait decrements the semaphore pointed by sem. If the semaphore value is non-zero, the decrement happens right away. If the semaphore value is zero, the call blocks till the time semaphore becomes greater than zero and the decrement is done. sem_wait returns zero on success and -1 on error. In case of error, the semaphore value is left unchanged and errno is set to the appropriate error number. sem_wait provides the call for the P operation for the semaphore.
2.4 sem_trywait
#include <semaphore.h> int sem_trywait (sem_t *sem);
sem_trywait is just like the sem_wait call, except that, if the semaphore value is zero, it does not block but returns immediately with errno set to EAGAIN. In other system calls we have flags like IPC_NOWAIT, but here, we have a full-fledged system call for that purpose.
2.5 sem_timedwait
#include <semaphore.h> int sem_timedwait (sem_t *sem, const struct timespec *abs_timeout);
sem_timedwait is also like the sem_wait call, except that, there is timer specified with the pointer, abs_timeout. If the semaphore value is greater than zero, it is decremented and the timeout value pointed by abs_timeout is not used. That is, in that case, the call works just like the sem_wait call. If the semaphore value is zero, the call blocks, the maximum duration of blocking being the time till the timer goes off. If the semaphore value becomes greater than zero during the blocking period, the semaphore is decremented immediately and the call returns. Otherwise, the timer goes off and the call returns with errno set to ETIMEDOUT. The timer is specified in the struct timespec, which is,
struct timespec { time_t tv_sec; /* Seconds */ long tv_nsec; /* Nanoseconds [0 .. 999999999] */ };
2.6 sem_getvalue
#include <semaphore.h> int sem_getvalue (sem_t *sem, int *sval);
sem_getvalue gets the value of semaphore pointed by sem. The value is returned in the integer pointed by sval. It returns 0 on success and -1 on error, with errno indicating the actual error.
2.7 sem_unlink
#include <semaphore.h> int sem_unlink (const char *name);
sem_unlink removes the semaphore associated with the name.
3.0 POSIX Named Semaphore Example Program
The example program implements a solution to the producer – consumer problem. The producers produce strings of text. The consumer thread prints the strings on the terminal.
/* * * posix-semaphore-example.c: Program to demonstrate POSIX semaphores * in C under Linux (Producer - Consumer problem) */ #include <sys/types.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <errno.h> #include <pthread.h> #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #include <unistd.h> #define SEM_MUTEX_NAME "/sem-mutex" #define SEM_BUFFER_COUNT_NAME "/sem-buffer-count" #define SEM_SPOOL_SIGNAL_NAME "/sem-spool-signal" // Buffer data structures #define MAX_BUFFERS 10 char buf [MAX_BUFFERS] [100]; int buffer_index; int buffer_print_index; sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem; void *producer (void *arg); void *spooler (void *arg); int main (int argc, char **argv) { pthread_t tid_producer [10], tid_spooler; int i, r; // initialization buffer_index = buffer_print_index = 0; // mutual exclusion semaphore, mutex_sem with an initial value 1. if ((mutex_sem = sem_open (SEM_MUTEX_NAME, O_CREAT, 0660, 1)) == SEM_FAILED) { perror ("sem_open"); exit (1); } // counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS if ((buffer_count_sem = sem_open (SEM_BUFFER_COUNT_NAME, O_CREAT, 0660, MAX_BUFFERS)) == SEM_FAILED) { perror ("sem_open"); exit (1); } // counting semaphore, indicating the number of strings to be printed. Initial value = 0 if ((spool_signal_sem = sem_open (SEM_SPOOL_SIGNAL_NAME, O_CREAT, 0660, 0)) == SEM_FAILED) { perror ("sem_open"); exit (1); } // Create spooler if ((r = pthread_create (&tid_spooler, NULL, spooler, NULL)) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // Create 10 producer threads int thread_no [10]; for (i = 0; i < 10; i++) { thread_no [i] = i; if ((r = pthread_create (&tid_producer [i], NULL, producer, (void *) &thread_no [i])) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } } // Wait for producers to terminate for (i = 0; i < 10; i++) if ((r = pthread_join (tid_producer [i], NULL)) == -1) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // No more strings to print? Wait for spool_signal_sem to become 0 int semval; while (1) { if (sem_getvalue (spool_signal_sem, &semval)== -1) perror ("sem_getvalue"); if (!semval) break; sleep (1); } // terminate spooler if ((r = pthread_cancel (tid_spooler)) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // Remove semaphores if (sem_unlink (SEM_MUTEX_NAME) == -1) { perror ("sem_unlink"); exit (1); } if (sem_unlink (SEM_BUFFER_COUNT_NAME) == -1) { perror ("sem_unlink"); exit (1); } if (sem_unlink (SEM_SPOOL_SIGNAL_NAME) == -1) { perror ("sem_unlink"); exit (1); } exit (0); } // producer: produce strings for printing // There might be multiple producer threads void *producer (void *arg) { // Create 10 strings and terminate int i; int my_id = *((int *) arg); int count = 0; for (i = 0; i < 10; i++) { // get a buffer: P (buffer_count_sem); if (sem_wait (buffer_count_sem) == -1) { perror ("sem_wait: buffer_count_sem"); exit (1); } /* There might be multiple producers. We must ensure that only one producer uses buffer_index at a time. */ // P (mutex_sem); if (sem_wait (mutex_sem) == -1) { perror ("sem_wait: mutex_sem"); exit (1); } // Critical section int j = buffer_index; buffer_index++; if (buffer_index == MAX_BUFFERS) buffer_index = 0; // Release mutex sem: V (mutex_sem) if (sem_post (mutex_sem) == -1) { perror ("sem_post: mutex_sem"); exit (1); } // Produce a string sprintf (buf [j], "Thread %d: %d\n", my_id, ++count); // Tell spooler that there is a string to print: V (spool_signal_sem); if (sem_post (spool_signal_sem) == -1) { perror ("sem_post: spool_signal_sem"); exit (1); } // Take a nap sleep (1); } } // There is only one spooler thread void *spooler (void *arg) { while (1) { // forever // Is there a string to print? P (spool_signal_sem); if (sem_wait (spool_signal_sem) == -1) { perror ("sem_wait: spool_signal_sem"); exit (1); } printf ("%s", buf [buffer_print_index]); /* Since there is only one thread (spooler) using the buffer_print_index, mutex semaphore is not necessary */ buffer_print_index++; if (buffer_print_index == MAX_BUFFERS) buffer_print_index = 0; /* Contents of one buffer has been printed. One more buffer is available for use by producers. Release buffer: V (buffer_count_sem); */ if (sem_post (buffer_count_sem) == -1) { perror ("sem_post: buffer_count_sem"); exit (1); } } }
We can compile and run the above program,
$ gcc posix-semaphore-example.c -o posix-semaphore-example -lpthread $ ./posix-semaphore-example Thread 1: 1 Thread 0: 1 Thread 2: 1 Thread 4: 1 ...
It is instructive to compare the above program with the equivalent program using the System V semaphores. The above program using POSIX semaphores is simpler and more compact.
4.0 POSIX Unnamed Semaphore calls
In the previous example, the semaphores are local to a process; they are only used by its threads. No other process uses them. So, it looks like a waste of effort to have system-wide semaphore names and use calls like sem_open. There are POSIX unnamed semaphores which can do what we need in a much simpler and efficient manner. First, the system calls,
4.1 sem_init
#include <semaphore.h> int sem_init (sem_t *sem, int pshared, unsigned int value);
sem_init is the equivalent of sem_open for unnamed semaphores. One defines a variable of type sem_t and passes its pointer as sem in the sem_init call. Or, one can define a pointer and allocate memory dynamically using malloc or a similar function call. sem_init initializes the semaphore pointed by sem with the value. The second argument pshared indicates whether the semaphore is shared between threads of a process or between processes. If pshared has the value 0, the semaphore is shared between threads of a process. The semaphore should be placed at a place where it is visible to all threads. If pshared has a nonzero value, it indicates that the semaphore is shared by processes. In that case the semaphore has to be placed in a shared memory segment which is attached to the concerned processes.
4.2 sem_destroy
#include <semaphore.h> int sem_destroy (sem_t *sem);
sem_destroy destroys the unnamed semaphore pointed by sem.
5.0 POSIX Unnamed Semaphore Example Program
Here is a version of the above program using unnamed semaphores in place of the named ones. Note that instead of pointers to semaphores, we have the actual semaphores defined as global variables and, hence, we need to make a pointer using the & operator in the semaphore calls.
/* * * posix-unnamed-semaphore-example.c: Program to demonstrate POSIX * unnamed semaphores in C under * Linux (Producer - Consumer problem) */ #include <sys/types.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <errno.h> #include <pthread.h> #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #include <unistd.h> // Buffer data structures #define MAX_BUFFERS 10 char buf [MAX_BUFFERS] [100]; int buffer_index; int buffer_print_index; sem_t mutex_sem, buffer_count_sem, spool_signal_sem; void *producer (void *arg); void *spooler (void *arg); int main (int argc, char **argv) { pthread_t tid_producer [10], tid_spooler; int i, r; // initialization buffer_index = buffer_print_index = 0; // mutual exclusion semaphore, mutex_sem with an initial value 1. if (sem_init (&mutex_sem, 0, 1) == -1) { perror ("sem_init"); exit (1); } // counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS if (sem_init (&buffer_count_sem, 0, MAX_BUFFERS) == -1) { perror ("sem_init"); exit (1); } // counting semaphore, indicating the number of strings to be printed. Initial value = 0 if (sem_init (&spool_signal_sem, 0, 0) == -1) { perror ("sem_init"); exit (1); } // Create spooler if ((r = pthread_create (&tid_spooler, NULL, spooler, NULL)) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // Create 10 producer threads int thread_no [10]; for (i = 0; i < 10; i++) { thread_no [i] = i; if ((r = pthread_create (&tid_producer [i], NULL, producer, (void *) &thread_no [i])) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } } // Wait for producers to terminate for (i = 0; i < 10; i++) if ((r = pthread_join (tid_producer [i], NULL)) == -1) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // No more strings to print? Wait for spool_signal_sem to become 0 int semval; while (1) { if (sem_getvalue (&spool_signal_sem, &semval)== -1) perror ("sem_getvalue"); if (!semval) break; sleep (1); } // terminate spooler if ((r = pthread_cancel (tid_spooler)) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // Destroy semaphores if (sem_destroy (&mutex_sem) == -1) { perror ("sem_destroy"); exit (1); } if (sem_destroy (&mutex_sem) == -1) { perror ("sem_destroy"); exit (1); } if (sem_destroy (&mutex_sem) == -1) { perror ("sem_destroy"); exit (1); } exit (0); } // producer: produce strings for printing // There might be multiple producer threads void *producer (void *arg) { // Create 10 strings and terminate int i; int my_id = *((int *) arg); int count = 0; for (i = 0; i < 10; i++) { // get a buffer: P (buffer_count_sem); if (sem_wait (&buffer_count_sem) == -1) { perror ("sem_wait: buffer_count_sem"); exit (1); } /* There might be multiple producers. We must ensure that only one producer uses buffer_index at a time. */ // P (mutex_sem); if (sem_wait (&mutex_sem) == -1) { perror ("sem_wait: mutex_sem"); exit (1); } // Critical section int j = buffer_index; buffer_index++; if (buffer_index == MAX_BUFFERS) buffer_index = 0; // Release mutex sem: V (mutex_sem) if (sem_post (&mutex_sem) == -1) { perror ("sem_post: mutex_sem"); exit (1); } // Produce a string sprintf (buf [j], "Thread %d: %d\n", my_id, ++count); // Tell spooler that there is a string to print: V (spool_signal_sem); if (sem_post (&spool_signal_sem) == -1) { perror ("sem_post: spool_signal_sem"); exit (1); } // Take a nap sleep (1); } } // There is only one spooler thread void *spooler (void *arg) { while (1) { // forever // Is there a string to print? P (spool_signal_sem); if (sem_wait (&spool_signal_sem) == -1) { perror ("sem_wait: spool_signal_sem"); exit (1); } printf ("%s", buf [buffer_print_index]); /* Since there is only one thread (spooler) using the buffer_print_index, mutex semaphore is not necessary */ buffer_print_index++; if (buffer_print_index == MAX_BUFFERS) buffer_print_index = 0; /* Contents of one buffer has been printed. One more buffer is available for use by producers. Release buffer: V (buffer_count_sem); */ if (sem_post (&buffer_count_sem) == -1) { perror ("sem_post: buffer_count_sem"); exit (1); } } }