libostd/ostd/generic_condvar.hh

158 lines
4.9 KiB
C++
Raw Permalink Normal View History

2017-03-30 00:36:51 +02:00
/** @addtogroup Concurrency
* @{
*/
/** @file generic_condvar.hh
2017-03-23 10:24:35 +01:00
*
2017-03-30 00:38:43 +02:00
* @brief A single type that can encapsulate different condvar types.
2017-03-30 00:36:51 +02:00
*
* @copyright See COPYING.md in the project tree for further information.
2017-03-23 10:24:35 +01:00
*/
#ifndef OSTD_GENERIC_CONDVAR_HH
#define OSTD_GENERIC_CONDVAR_HH
#include <type_traits>
#include <algorithm>
#include <condition_variable>
2020-09-19 04:16:26 +02:00
#include <ostd/platform.hh>
2017-03-23 10:24:35 +01:00
namespace ostd {
2017-03-30 00:36:51 +02:00
/** @addtogroup Concurrency
* @{
*/
2017-03-23 10:24:35 +01:00
namespace detail {
2020-09-19 04:16:26 +02:00
struct OSTD_EXPORT cond_iface {
2017-03-23 10:24:35 +01:00
cond_iface() {}
2018-01-05 22:31:04 +01:00
virtual ~cond_iface();
2017-03-23 10:24:35 +01:00
virtual void notify_one() = 0;
virtual void notify_all() = 0;
virtual void wait(std::unique_lock<std::mutex> &) = 0;
};
template<typename C>
struct cond_impl: cond_iface {
cond_impl(): p_cond() {}
template<typename F>
cond_impl(F &func): p_cond(func()) {}
void notify_one() {
p_cond.notify_one();
}
void notify_all() {
p_cond.notify_all();
}
void wait(std::unique_lock<std::mutex> &l) {
p_cond.wait(l);
}
private:
C p_cond;
};
} /* namespace detail */
2017-03-30 00:36:51 +02:00
/** @brief A generic condition variable type.
*
* This is a type that implements a condition variable interface but can
* encapsulate different real condition variable types while still having
* just one static type. This is useful when you need to implement a data
* structure that requires a condition variable but still want it to be
* compatible with custom schedulers (which can use a custom condition
* variable implementation for their logical threads) without having to
* template it.
*
* The storage for the custom type is at least 6 pointers, depending on
* the size of a standard std::condition_variable (if it's bigger, the
* space is the size of that).
*/
2017-03-23 10:24:35 +01:00
struct generic_condvar {
2017-04-12 19:12:09 +02:00
/** @brief Constructs the condvar using std::condition_variable. */
2017-03-23 10:24:35 +01:00
generic_condvar() {
new (reinterpret_cast<void *>(&p_condbuf))
detail::cond_impl<std::condition_variable>();
}
2017-03-30 00:36:51 +02:00
/** @brief Constructs the condvar using a custom type.
*
* As condvars don't have to be move constructible and your own
* condvar type can internally contain some custom state, the
* condvar to store is constructed using a function, which is
* required to return it.
*
* @param[in] func The function that is called to get the condvar.
*/
2017-03-23 10:24:35 +01:00
template<typename F>
generic_condvar(F &&func) {
new (reinterpret_cast<void *>(&p_condbuf))
detail::cond_impl<std::result_of_t<F()>>(func);
}
generic_condvar(generic_condvar const &) = delete;
generic_condvar(generic_condvar &&) = delete;
generic_condvar &operator=(generic_condvar const &) = delete;
generic_condvar &operator=(generic_condvar &&) = delete;
2017-04-12 19:12:09 +02:00
/** @brief Destroys the stored condvar. */
2017-03-23 10:24:35 +01:00
~generic_condvar() {
reinterpret_cast<detail::cond_iface *>(&p_condbuf)->~cond_iface();
}
2017-03-30 00:36:51 +02:00
/** @brief Notifies one waiting thread.
*
* If any threads are waiting on this condvar, this unblocks one of
* them. The actual semantics and what the threads are are defined by
* the condition variable type that is stored. This simply calls
* `.notify_one()` on the stored condvar.
*
* @see notify_all(), wait(std::unique_lock<std::mutex>)
*/
2017-03-23 10:24:35 +01:00
void notify_one() {
reinterpret_cast<detail::cond_iface *>(&p_condbuf)->notify_one();
}
2017-03-30 00:36:51 +02:00
/** @brief Notifies all waiting threads.
*
* If any threads are waiting on this condvar, this unblocks all of
* them. The actual semantics and what the threads are are defined by
* the condition variable type that is stored. This simply calls
* `.notify_all()` on the stored condvar.
*
* @see notify_one(), wait(std::unique_lock<std::mutex>)
*/
2017-03-23 10:24:35 +01:00
void notify_all() {
reinterpret_cast<detail::cond_iface *>(&p_condbuf)->notify_all();
}
2017-03-30 00:36:51 +02:00
/** @brief Blocks the current thread until the condvar is woken up.
*
* This atomically releases the given lock, blocks the current thread
* and adds it to the waiting threads list, until unblocked by notify_one()
* or notify_all(). It may also be unblocked spuriously, depending on the
* implementation. The actual specific semantics and what the current
* thread is depends on the implementation of the stored condvar. This
* simply calls `.wait(l)` on it.
*
* @see notify_one(), notify_all()
*/
2017-03-23 10:24:35 +01:00
void wait(std::unique_lock<std::mutex> &l) {
reinterpret_cast<detail::cond_iface *>(&p_condbuf)->wait(l);
}
private:
2017-04-09 16:44:45 +02:00
static constexpr auto cvars = sizeof(std::condition_variable);
static constexpr auto icvars =
2017-03-30 00:36:51 +02:00
sizeof(detail::cond_impl<std::condition_variable>);
2017-03-23 10:24:35 +01:00
std::aligned_storage_t<std::max(
2017-03-30 00:36:51 +02:00
6 * sizeof(void *) + (icvars - cvars), icvars
2017-03-23 10:24:35 +01:00
)> p_condbuf;
};
2017-03-30 00:36:51 +02:00
/** @} */
2017-03-23 10:24:35 +01:00
} /* namespace ostd */
#endif
2017-03-30 00:36:51 +02:00
/** @} */