Open In App

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.

  1. It is a generalization of a mutex or a binary semaphore.
  2. You can initialize a counting semaphore with an initial count, which represents the number of threads that can access the resource simultaneously without blocking.
  3. Threads can acquire and release counts, and the semaphore’s count is incremented or decremented accordingly.
  4. If a thread tries to acquire more counts than are available, it will block until counts become available.

Example




// 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.

  1. It is often used for basic mutual exclusion or signaling between two threads.
  2. It can be thought of as a mutex with a more lightweight interface.

Example




// 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:

  1. Fine-Grained Control: To access a resource concurrently, semaphores can be configured to allow a specific number of threads.
  2. Generalization: Semaphores are more versatile and can be used to implement other synchronization primitives.
  3. 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).
  4. Blocking and Waiting: Blocking and waiting mechanisms in semaphores allow threads to wait until a resource becomes available again.
  5. Timeouts: A timeout could be specified when acquiring a semaphore, which makes it more useful.

Article Tags :