Skip to content

semaphore.hpp

Alairion edited this page May 8, 2021 · 11 revisions

semaphore.hpp

Not Enough Standards' semaphores are defined in header <nes/semaphore.hpp>

Description

Semaphores are synchronization primitives that use a resource counter. When trying to acquire a resource, the calling thread is blocked until a resource is available.

Unlike mutexes, semaphores don't have any notion of ownership: any thread can acquire or release a resource, at any time.

nes::semaphore is the default semaphore implementation. A resource of nes::semaphore can be acquired or release. You can also try to acquire a resource, in order to prevent the calling thread from waiting if no resource is available.

nes::timed_semaphore provide the same interface as nes::semaphore, plus two functions. One of them tries to acquire a resource in a certain delay, when the other tries to acquire it until a specific time point is reached.

Synopsis

namespace nes
{

class semaphore
{
public:
    using native_handle_type = /*implementation-defined*/;

public:
    explicit semaphore(std::size_t initial_count = 0);

    ~semaphore();

    semaphore(const semaphore&) = delete;
    semaphore& operator=(const semaphore&) = delete;

    semaphore(semaphore&& other) noexcept = delete;
    semaphore& operator=(semaphore&& other) noexcept = delete;

    void acquire();
    bool try_acquire();

    void release();

    native_handle_type native_handle() const noexcept;
};

class timed_semaphore
{
public:
    using native_handle_type = /*implementation-defined*/;

public:
    explicit timed_semaphore(std::size_t initial_count = 0);

    ~timed_semaphore();

    timed_semaphore(const timed_semaphore&) = delete;
    timed_semaphore& operator=(const timed_semaphore&) = delete;

    timed_semaphore(timed_semaphore&& other) noexcept = delete;
    timed_semaphore& operator=(timed_semaphore&& other) noexcept = delete;

    void acquire();
    bool try_acquire();

    template<typename Rep, typename Period>
    bool try_acquire_for(const std::chrono::duration<Rep, Period>& timeout);
    template<typename Clock, typename Duration>
    bool try_acquire_until(const std::chrono::time_point<Clock, Duration>& time_point);

    void release();

    native_handle_type native_handle() const noexcept;
};

}

Example

Here is an example in which we have a thread that give resources to another thread:

#include <thread>
#include <iostream>
#include <array>

#include <nes/semaphore.hpp>

void another_thread(const std::array<std::uint64_t, 8>& data, nes::semaphore& semaphore)
{
    const auto tp1{std::chrono::high_resolution_clock::now()};
    for(std::size_t i{}; i < 8; ++i)
    {
        //Wait until the next resource is available
        semaphore.acquire();

        const auto elapsed_time{std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - tp1)};
        std::cout << "Value " << i << " ready after " << elapsed_time.count() << "ms: " << data[i] << std::endl;
    }
}

int main()
{
    std::array<std::uint64_t, 8> data{0, 1};
    //Two resources are already available
    nes::semaphore semaphore{2};

    std::thread thread{another_thread, std::cref(data), std::ref(semaphore)};

    for(std::size_t i{2}; i < 8; ++i)
    {
        //Simulate a long processing time
        std::this_thread::sleep_for(std::chrono::milliseconds{250});
        data[i] = i * i;
        //Increment the resource counter of the semaphore
        semaphore.release();
    }

    if(thread.joinable())
        thread.join();
}

Possible output

Value 0 ready after 0ms: 0
Value 1 ready after 0ms: 1
Value 2 ready after 249ms: 4
Value 3 ready after 499ms: 9
Value 4 ready after 749ms: 16
Value 5 ready after 1000ms: 25
Value 6 ready after 1251ms: 36
Value 7 ready after 1501ms: 49
Clone this wiki locally