14.3.2. Semaphores

Semaphores are commonly used in operating systems and concurrent programs where the goal is to manage concurrent access to a pool of resources. When using a semaphore, the goal isn’t who owns what, but how many resources are still available. Semaphores are different from mutexes in several ways:

  • semaphores need not be in a binary (locked or unlocked) state. A special type of semaphore called a counting semaphore can range in value from 0 to some r, where r is the number of possible resources. Any time a resource is produced, the semaphore is incremented. Any time a resource is being used, the semaphore is decremented. When a counting semaphore has a value of 0, it means that no resources are available, and any other threads that attempt to acquire a resource must wait (e.g. block).

  • semaphores can be locked by default.

While a mutex and condition variables can simulate the functionality of a semaphore, using a semaphore may be simpler and more efficient in some cases. Semaphores also have the advantage that any thread can unlock the semaphore (in contrast to a mutex, where the calling thread must unlock it).

Semaphores are not part of the Pthreads library, but that does not mean that you cannot use them. On Linux and OS X systems, semaphore primitives can be accessed from semaphore.h, which is typically located in /usr/include. Since there is no standard, the function calls may differ on different systems. That said, the semaphore library has similar declaration to that of mutexes:

  • declaring a semaphore (type sem_t, e.g. sem_t semaphore).

  • initialize a semaphore using sem_init() (usually in main()). The sem_init() function has three parameters. The first is the address of a semaphore, the second is its initial state (locked or unlocked), and its third parameter indicates if the semaphore should be shared with the threads of a process (e.g. 0) or between processes (e.g. 1). This is useful because semaphores are commonly used for process synchronization. For example, initializing a semaphore with the call sem_init(&semaphore, 1, 0) indicates that our semaphore is initially locked (second parameter is 1), and is to be shared amongst the threads of a common process (third parameter is 0). In contrast, mutexes always start out unlocked. It is important to note that in OS X, the equivalent function is sem_open().

  • destroy a semaphore using sem_destroy() (usually in main()). This function only takes a pointer to the semaphore (sem_destroy(&semaphore)). Note that in OS X, the equivalent function may be sem_unlink() or sem_close().

  • The sem_wait() function indicates that a resource is being used, and decrements the semaphore. If the semaphore’s value is greater than 0 (indicating there are still resources available), the function will immediately return, and the thread is allowed to proceed. If the semaphore’s value is already 0, the thread will block until a resource becomes available (i.e. the semaphore has a positive value). A call to sem_wait() typically looks like sem_wait(&semaphore).

  • The sem_post() function indicates that a resource is being freed, and increments the semaphore. This function returns immediately. If there is a thread waiting on the semaphore (i.e., the semaphore’s value was previously 0), then the other thread will take ownership of the resource freed. A call to sem_post() looks like sem_post(&semaphore).