Skip to content

Instantly share code, notes, and snippets.

@frosch123
Last active March 7, 2022 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frosch123/7cedfd459d1e0c060af7342db41443f7 to your computer and use it in GitHub Desktop.
Save frosch123/7cedfd459d1e0c060af7342db41443f7 to your computer and use it in GitHub Desktop.
std::counting_semaphore does not meet BasicLockable requirements
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