C++20stladvanced
std::counting_semaphore / std::binary_semaphore (C++20 Addition)
C++20 · The Big Four · Compiler flag: -std=c++20
Explanation
Lightweight synchronization primitive that maintains a counter. `acquire()` blocks until the counter is positive, then decrements it. `release()` increments the counter. `binary_semaphore` is a specialization with max count of 1.
Real-World Scenario
Limit the number of concurrent database connections in a connection pool.
Quick Snippet
snippet.cpp
#include <semaphore>
#include <thread>
std::counting_semaphore<10> pool(5); // max 10, initial 5
void use_connection() {
pool.acquire(); // blocks if no connections available
// use database connection...
pool.release(); // return connection to pool
}Full Example
semaphore.cpp
// CodeNebula - C++20 Example: std::counting_semaphore and binary_semaphore
// Compile: g++ -std=c++20 -Wall -Wextra -pthread -o example semaphore.cpp
#include <iostream>
#include <semaphore>
#include <thread>
#include <vector>
int main() {
std::cout << "=== std::counting_semaphore ===\n";
std::cout << "(Limits concurrent access to a resource)\n\n";
// Allow up to 3 concurrent workers (like a connection pool)
std::counting_semaphore<3> pool(3);
auto worker = [&pool](int id) {
pool.acquire(); // Wait if count is 0, then decrement
std::cout << "Worker " << id << " acquired resource\n";
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::cout << "Worker " << id << " releasing resource\n";
pool.release(); // Increment count, potentially unblocking a waiter
};
std::vector<std::jthread> threads;
for (int i = 0; i < 6; ++i) {
threads.emplace_back(worker, i);
}
for (auto& t : threads) t.join();
std::cout << "\n=== std::binary_semaphore ===\n";
std::cout << "(Equivalent to counting_semaphore<1>, like a mutex)\n\n";
// binary_semaphore for signaling between threads
std::binary_semaphore signal(0); // Start with 0 (blocked)
std::jthread producer([&signal] {
std::cout << "Producer: preparing data...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(30));
std::cout << "Producer: data ready, signaling consumer\n";
signal.release(); // Signal the consumer
});
std::jthread consumer([&signal] {
std::cout << "Consumer: waiting for data...\n";
signal.acquire(); // Block until producer signals
std::cout << "Consumer: received signal, processing data\n";
});
producer.join();
consumer.join();
std::cout << "\n=== try_acquire (non-blocking) ===\n";
std::counting_semaphore<2> limited(2);
limited.acquire();
limited.acquire(); // Both slots taken
if (limited.try_acquire()) {
std::cout << "Unexpected: acquired when full\n";
} else {
std::cout << "try_acquire returned false (as expected, no slots)\n";
}
limited.release(); // Free one slot
if (limited.try_acquire()) {
std::cout << "try_acquire succeeded after release\n";
limited.release();
}
std::cout << "\n=== Ping-pong with binary_semaphores ===\n";
std::binary_semaphore ping(1); // Start with ping
std::binary_semaphore pong(0);
std::jthread pinger([&] {
for (int i = 0; i < 3; ++i) {
ping.acquire();
std::cout << "Ping! ";
pong.release();
}
});
std::jthread ponger([&] {
for (int i = 0; i < 3; ++i) {
pong.acquire();
std::cout << "Pong! ";
ping.release();
}
});
pinger.join();
ponger.join();
std::cout << "\nDone\n";
return 0;
}
Run command
g++ -std=c++20 -Wall -Wextra -pthread -o example semaphore.cpp