std::counting_semaphore does not meet BasicLockable requirements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Title: Make std::counting_semaphore meet requirements of BasicLockable/Lockable/TimedLockable | |
Section: [thread.sema.cnt] | |
Discussion: | |
One of std::counting_semaphore's use-cases is to limit the number of active threads. | |
Shortened example from https://en.cppreference.com/w/cpp/thread/counting_semaphore/acquire : | |
constexpr std::ptrdiff_t max_sema_threads{3}; | |
std::counting_semaphore semaphore{max_sema_threads}; | |
void workerThread() | |
{ | |
semaphore.acquire(); // wait until a free sema slot is available | |
// do some work, maybe throw exception | |
semaphore.release(); | |
} | |
Ideally acquire/release in the worker thread should use RAII to guarantee release in case of exceptions. | |
However, std::counting_semaphore does not meet the requirements of BasicLockable or Lockable, so std::lock_guard and std::scoped_lock cannot be used. | |
Proposed resolution: | |
Add aliases to existing methods to match the requirements of Lockable and TimedLockable: | |
template<ptrdiff_t least_max_value = /* implementation-defined */> | |
class counting_semaphore { | |
public: | |
static constexpr ptrdiff_t max() noexcept; | |
constexpr explicit counting_semaphore(ptrdiff_t desired); | |
~counting_semaphore(); | |
counting_semaphore(const counting_semaphore&) = delete; | |
counting_semaphore& operator=(const counting_semaphore&) = delete; | |
void release(ptrdiff_t update = 1); | |
void acquire(); | |
bool try_acquire() noexcept; | |
template<class Rep, class Period> | |
bool try_acquire_for(const chrono::duration<Rep, Period>& rel_time); | |
template<class Clock, class Duration> | |
bool try_acquire_until(const chrono::time_point<Clock, Duration>& abs_time); | |
<ins> | |
void unlock() { release(); } | |
void lock() { acquire(); } | |
bool try_lock() noexcept { return try_acquire(); } | |
template<class Rep, class Period> | |
bool try_lock_for(const chrono::duration<Rep, Period>& rel_time) { return try_acquire_for(rel_time); } | |
template<class Clock, class Duration> | |
bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time) { return try_acquire_until(abs_time); } | |
</ins> | |
}; | |
Notes: | |
(1) Alternatively "unlock" could be declared as | |
void unlock(ptrdiff_t update = 1) { release(update); } | |
This would work for usage in templates like std::lock_guard and std::scoped_lock, but differ from the requirements of BasicLockable. | |
(2) Formally BasicLockable requires unlock() to be "noexcept", but std::mutex::unlock() is not declared "noexcept" either (though implementations probably do not throw). | |
(3) counting_semaphore could be considered a SharedLockable, because it is not exclusive. However this does not add anything to usability, and makes it unsuitable for std::scoped_lock and waiting for multiple objects. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment