C++ 20 – <semaphore> Header
The C++20 <semaphore> header is part of the Concurrency Library Technical Specification (TS). Semaphores are synchronization primitives that help control access to shared resources in multi-threaded programs. The <semaphore> header provides the standard C++ way to work with semaphores.
In this article, we have covered important sections of semaphore headers such as the main classes, and usage of semaphore headers in C++20 along with examples.
How to use <semaphore> in C++20?
Below is the step-by-step tutorial on the use of semaphores in C++ programs.
STEP 1: Include the Header
To use semaphores in your C++ program, you need to include the <semaphore> header:
#include <semaphore>
STEP 2: Semaphore Basics
You can create a semaphore object like this:
std::counting_semaphore<size_t> sem(1); // Initialize a semaphore with an initial count of 1
std::counting_semaphore is a type of semaphore that allows a specified number of threads to access a resource concurrently. In this example, only one thread can access the resource protected by sem at a time.
STEP 3: Acquiring and Releasing
There are 3 methods to acquire and release a semaphore object:
Method 1: Aquire and Release
To acquire (lock) the semaphore, you can use the acquire method:
sem.acquire(); // Critical section code sem.release();
The acquire method decreases the semaphore count by one, effectively locking it. The release method increases the count, releasing the semaphore.
Method 2: Try-Aquire
You can also use the try_acquire method to try to acquire the semaphore without blocking:
if (sem.try_acquire()) { // Successfully acquired the semaphore // Critical section code sem.release(); } else { // Semaphore was not acquired }
Method 2: Waiting with Timeout
C++20 also introduced the try_acquire_for and try_acquire_until methods to try acquiring the semaphore with a timeout.
if (sem.try_acquire_for(std::chrono::seconds(1))) { // Successfully acquired the semaphore within 1 second // Critical section code sem.release(); } else { // Semaphore was not acquired within 1 second }
Types of Semaphores
The <semaphores> header provides two types of semaphores that are:
1. std::counting_semaphore
A counting semaphore is a synchronization primitive that allows multiple threads to access a shared resource up to a certain limit.
- It is a generalization of a mutex or a binary semaphore.
- You can initialize a counting semaphore with an initial count, which represents the number of threads that can access the resource simultaneously without blocking.
- Threads can acquire and release counts, and the semaphore’s count is incremented or decremented accordingly.
- If a thread tries to acquire more counts than are available, it will block until counts become available.
Example
C++
// C++ Program to illustrate the use of counting_semaphore #include <iostream> #include <semaphore> #include <thread> using namespace std; // Initialize semaphore with a count of 3 counting_semaphore<10> semaphore(3); void worker( int id) { // aquiring semaphore.acquire(); // doing some work cout << "Thread " << id << " acquired the semaphore." << endl; // releasing semaphore.release(); cout << "Thread " << id << " released the semaphore." << endl; } // driver code int main() { thread t1(worker, 1); thread t2(worker, 2); thread t3(worker, 3); t1.join(); t2.join(); t3.join(); return 0; } |
Output
Thread 2 acquired the semaphore. Thread 2 released the semaphore. Thread 1 acquired the semaphore. Thread 1 released the semaphore. Thread 3 acquired the semaphore. Thread 3 released the semaphore.
2. std::binary_semaphore
A binary semaphore is a simpler version of a semaphore that can have only two values: 0 and 1.
- It is often used for basic mutual exclusion or signaling between two threads.
- It can be thought of as a mutex with a more lightweight interface.
Example
C++
// C++ program to illustrate the binary semaphores #include <iostream> #include <semaphore> #include <thread> using namespace std; // Initialize with a count of 1 (binary) binary_semaphore semaphore(1); void worker( int id) { // aquire semaphore semaphore.acquire(); cout << "Thread " << id << " acquired the semaphore." << endl; // Do some work semaphore.release(); // release cout << "Thread " << id << " released the semaphore." << endl; } // driver code int main() { thread t1(worker, 1); thread t2(worker, 2); t1.join(); t2.join(); return 0; } |
Output
Thread 1 acquired the semaphore. Thread 1 released the semaphore. Thread 2 acquired the semaphore. Thread 2 released the semaphore.
Advantages of Semaphores
The following are some main advantages of using semaphores over similar constructs:
- Fine-Grained Control: To access a resource concurrently, semaphores can be configured to allow a specific number of threads.
- Generalization: Semaphores are more versatile and can be used to implement other synchronization primitives.
- Multiple Resources: Counting semaphores can be used to manage multiple instances of a resource. This makes them suitable for scenarios where you need to control access to a pool of resources (e.g., a thread pool or a connection pool).
- Blocking and Waiting: Blocking and waiting mechanisms in semaphores allow threads to wait until a resource becomes available again.
- Timeouts: A timeout could be specified when acquiring a semaphore, which makes it more useful.
Contact Us