std::counting_semaphore::acquire(3)

NAME
std::counting_semaphore::acquire - std::counting_semaphore::acquire

Synopsis
   void acquire(); (since C++20)
   Atomically decrements the internal counter by 1 if it is greater than 0;
   otherwise blocks until it is greater than 0 and can successfully decrement the
   internal counter.

Preconditions
   (none)

Parameters
   (none)

Exceptions
   May throw std::system_error.

Example
   The example visualizes a concurrent work of several randomized threads when no more
   than N (N is the semaphore desired value) of the thread-functions are active, while
   the other might wait on the semaphore. // Run this code
 #include <array>
 #include <chrono>
 #include <cstddef>
 #include <iomanip>
 #include <iostream>
 #include <mutex>
 #include <random>
 #include <semaphore>
 #include <thread>
 #include <vector>
 using namespace std::literals;
 constexpr std::size_t max_threads{10U}; // change and see the effect
 constexpr std::ptrdiff_t max_sema_threads{3}; // {1} for binary semaphore
 std::counting_semaphore semaphore{max_sema_threads};
 constexpr auto time_tick{10ms};
 unsigned rnd() {
     static std::uniform_int_distribution<unsigned> distribution{2U, 9U}; // [delays]
     static std::random_device engine;
     static std::mt19937 noise{engine()};
     return distribution(noise);
 }
 class alignas( 128 /*std::hardware_destructive_interference_size*/ ) Guide {
     inline static std::mutex cout_mutex;
     inline static std::chrono::time_point<std::chrono::high_resolution_clock> started_at;
     unsigned delay{rnd()}, occupy{rnd()}, wait_on_sema{};
   public:
     static void start_time() { started_at = std::chrono::high_resolution_clock::now(); }
     void initial_delay() { std::this_thread::sleep_for(delay * time_tick); }
     void occupy_sema() {
         wait_on_sema =
             static_cast<unsigned>(std::chrono::duration_cast<std::chrono::milliseconds>(
               std::chrono::high_resolution_clock::now() - started_at - delay * time_tick)
                 .count() / time_tick.count());
         std::this_thread::sleep_for(occupy * time_tick);
     }
     void visualize(unsigned id, unsigned x_scale = 2) const {
         auto cout_n = [=] (auto str, unsigned n) {
             n *= x_scale;
             while (n-- > 0) { std::cout << str; }
         };
         std::lock_guard lk{cout_mutex};
         std::cout << "#" << std::setw(2) << id << " ";
         cout_n("░", delay);
         cout_n("▒", wait_on_sema);
         cout_n("█", occupy);
         std::cout << '\n';
     }
     static void show_info() {
         std::cout
             << "\nThreads: " << max_threads << ", Throughput: " << max_sema_threads
             << " │ Legend: initial delay ░░ │ wait state ▒▒ │ sema occupation ██ \n"
             << std::endl;
     }
 };
 std::array<Guide, max_threads> guides;
 void workerThread(unsigned id) {
     guides[id].initial_delay(); // emulate some work before sema acquisition
     semaphore.acquire(); // wait until a free sema slot is available
     guides[id].occupy_sema(); // emulate some work while sema is acquired
     semaphore.release();
     guides[id].visualize(id);
 }
 int main() {
     std::vector<std::jthread> threads;
     threads.reserve(max_threads);
     Guide::show_info();
     Guide::start_time();
     for (auto id{0U}; id != max_threads; ++id) {
         threads.push_back(std::jthread(workerThread, id));
     }
 }

Possible output:
 Default case: max_threads{10U}, max_sema_threads{3}
 Threads: 10, Throughput: 3 │ Legend: initial delay ░░ │ wait state ▒▒ │ sema occupation ██
 # 1 ░░░░██████
 # 2 ░░░░████████
 # 5 ░░░░░░██████████
 # 8 ░░░░░░░░░░░░████████████
 # 9 ░░░░░░░░░░░░██████████████
 # 7 ░░░░░░░░░░░░▒▒▒▒████████████████
 # 4 ░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒████████
 # 6 ░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒██████████████████
 # 3 ░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████████
 # 0 ░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████
 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
 "Enough for everyone" case (no wait states!): max_threads{10U}, max_sema_threads{10}
 Threads: 10, Throughput: 10 │ Legend: initial delay ░░ │ wait state ▒▒ │ sema occupation ██
 # 4 ░░░░██████
 # 5 ░░░░░░████
 # 3 ░░░░██████████
 # 1 ░░░░██████████
 # 8 ░░░░░░░░████████████
 # 6 ░░░░░░░░░░░░░░░░██████
 # 7 ░░░░░░░░░░░░░░░░██████
 # 9 ░░░░░░░░░░░░░░░░██████████
 # 0 ░░░░░░░░░░░░██████████████████
 # 2 ░░░░░░░░░░░░░░░░░░████████████
 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Binary semaphore case: max_threads{10U}, max_sema_threads{1}
 Threads: 10, Throughput: 1 │ Legend: initial delay ░░ │ wait state ▒▒ │ sema occupation ██
 # 6 ░░░░████
 # 5 ░░░░▒▒▒▒████
 # 4 ░░░░░░░░░░▒▒██████████
 # 7 ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒████████████████
 # 2 ░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████
 # 3 ░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████████████
 # 0 ░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████████
 # 1 ░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████
 # 8 ░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████
 # 9 ░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████