| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | #pragma once |
| |
|
| | #include <version> |
| |
|
| | #ifdef __cpp_lib_jthread |
| |
|
| | #include <stop_token> |
| | #include <thread> |
| |
|
| | namespace Common { |
| |
|
| | template <typename Condvar, typename Lock, typename Pred> |
| | void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { |
| | cv.wait(lock, token, std::move(pred)); |
| | } |
| |
|
| | } |
| |
|
| | #else |
| |
|
| | #include <atomic> |
| | #include <functional> |
| | #include <map> |
| | #include <memory> |
| | #include <mutex> |
| | #include <optional> |
| | #include <thread> |
| | #include <type_traits> |
| | #include <utility> |
| |
|
| | namespace std { |
| | namespace polyfill { |
| |
|
| | using stop_state_callback = std::size_t; |
| |
|
| | class stop_state { |
| | public: |
| | stop_state() = default; |
| | ~stop_state() = default; |
| |
|
| | bool request_stop() { |
| | unique_lock lk{m_lock}; |
| |
|
| | if (m_stop_requested) { |
| | |
| | return false; |
| | } |
| |
|
| | |
| | m_stop_requested = true; |
| |
|
| | while (!m_callbacks.empty()) { |
| | |
| | const auto it = m_callbacks.begin(); |
| |
|
| | |
| | function<void()> f; |
| | swap(it->second, f); |
| |
|
| | |
| | m_callbacks.erase(it); |
| |
|
| | |
| | if (f) { |
| | f(); |
| | } |
| | } |
| |
|
| | return true; |
| | } |
| |
|
| | bool stop_requested() const { |
| | unique_lock lk{m_lock}; |
| | return m_stop_requested; |
| | } |
| |
|
| | stop_state_callback insert_callback(function<void()> f) { |
| | unique_lock lk{m_lock}; |
| |
|
| | if (m_stop_requested) { |
| | |
| | |
| | if (f) { |
| | f(); |
| | } |
| | return 0; |
| | } |
| |
|
| | |
| | stop_state_callback ret = ++m_next_callback; |
| | m_callbacks.emplace(ret, std::move(f)); |
| | return ret; |
| | } |
| |
|
| | void remove_callback(stop_state_callback cb) { |
| | unique_lock lk{m_lock}; |
| | m_callbacks.erase(cb); |
| | } |
| |
|
| | private: |
| | mutable recursive_mutex m_lock; |
| | map<stop_state_callback, function<void()>> m_callbacks; |
| | stop_state_callback m_next_callback{0}; |
| | bool m_stop_requested{false}; |
| | }; |
| |
|
| | } |
| |
|
| | #ifndef __cpp_lib_concepts |
| | template <class T, class... Args> |
| | concept constructible_from = is_nothrow_destructible_v<T> && is_constructible_v<T, Args...>; |
| | #endif |
| |
|
| | class stop_token; |
| | class stop_source; |
| | struct nostopstate_t { |
| | explicit nostopstate_t() = default; |
| | }; |
| | inline constexpr nostopstate_t nostopstate{}; |
| |
|
| | template <class Callback> |
| | class stop_callback; |
| |
|
| | class stop_token { |
| | public: |
| | stop_token() noexcept = default; |
| |
|
| | stop_token(const stop_token&) noexcept = default; |
| | stop_token(stop_token&&) noexcept = default; |
| | stop_token& operator=(const stop_token&) noexcept = default; |
| | stop_token& operator=(stop_token&&) noexcept = default; |
| | ~stop_token() = default; |
| |
|
| | void swap(stop_token& other) noexcept { |
| | m_stop_state.swap(other.m_stop_state); |
| | } |
| |
|
| | [[nodiscard]] bool stop_requested() const noexcept { |
| | return m_stop_state && m_stop_state->stop_requested(); |
| | } |
| | [[nodiscard]] bool stop_possible() const noexcept { |
| | return m_stop_state != nullptr; |
| | } |
| |
|
| | private: |
| | friend class stop_source; |
| | template <typename Callback> |
| | friend class stop_callback; |
| | stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {} |
| |
|
| | private: |
| | shared_ptr<polyfill::stop_state> m_stop_state; |
| | }; |
| |
|
| | class stop_source { |
| | public: |
| | stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {} |
| | explicit stop_source(nostopstate_t) noexcept {} |
| |
|
| | stop_source(const stop_source&) noexcept = default; |
| | stop_source(stop_source&&) noexcept = default; |
| | stop_source& operator=(const stop_source&) noexcept = default; |
| | stop_source& operator=(stop_source&&) noexcept = default; |
| | ~stop_source() = default; |
| | void swap(stop_source& other) noexcept { |
| | m_stop_state.swap(other.m_stop_state); |
| | } |
| |
|
| | [[nodiscard]] stop_token get_token() const noexcept { |
| | return stop_token(m_stop_state); |
| | } |
| | [[nodiscard]] bool stop_possible() const noexcept { |
| | return m_stop_state != nullptr; |
| | } |
| | [[nodiscard]] bool stop_requested() const noexcept { |
| | return m_stop_state && m_stop_state->stop_requested(); |
| | } |
| | bool request_stop() noexcept { |
| | return m_stop_state && m_stop_state->request_stop(); |
| | } |
| |
|
| | private: |
| | friend class jthread; |
| | explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) |
| | : m_stop_state(std::move(stop_state)) {} |
| |
|
| | private: |
| | shared_ptr<polyfill::stop_state> m_stop_state; |
| | }; |
| |
|
| | template <typename Callback> |
| | class stop_callback { |
| | static_assert(is_nothrow_destructible_v<Callback>); |
| | static_assert(is_invocable_v<Callback>); |
| |
|
| | public: |
| | using callback_type = Callback; |
| |
|
| | template <typename C> |
| | requires constructible_from<Callback, C> |
| | explicit stop_callback(const stop_token& st, |
| | C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) |
| | : m_stop_state(st.m_stop_state) { |
| | if (m_stop_state) { |
| | m_callback = m_stop_state->insert_callback(std::move(cb)); |
| | } |
| | } |
| | template <typename C> |
| | requires constructible_from<Callback, C> |
| | explicit stop_callback(stop_token&& st, |
| | C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) |
| | : m_stop_state(std::move(st.m_stop_state)) { |
| | if (m_stop_state) { |
| | m_callback = m_stop_state->insert_callback(std::move(cb)); |
| | } |
| | } |
| | ~stop_callback() { |
| | if (m_stop_state && m_callback) { |
| | m_stop_state->remove_callback(m_callback); |
| | } |
| | } |
| |
|
| | stop_callback(const stop_callback&) = delete; |
| | stop_callback(stop_callback&&) = delete; |
| | stop_callback& operator=(const stop_callback&) = delete; |
| | stop_callback& operator=(stop_callback&&) = delete; |
| |
|
| | private: |
| | shared_ptr<polyfill::stop_state> m_stop_state; |
| | polyfill::stop_state_callback m_callback; |
| | }; |
| |
|
| | template <typename Callback> |
| | stop_callback(stop_token, Callback) -> stop_callback<Callback>; |
| |
|
| | class jthread { |
| | public: |
| | using id = thread::id; |
| | using native_handle_type = thread::native_handle_type; |
| |
|
| | jthread() noexcept = default; |
| |
|
| | template <typename F, typename... Args, |
| | typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> |
| | explicit jthread(F&& f, Args&&... args) |
| | : m_stop_state(make_shared<polyfill::stop_state>()), |
| | m_thread(make_thread(std::move(f), std::move(args)...)) {} |
| |
|
| | ~jthread() { |
| | if (joinable()) { |
| | request_stop(); |
| | join(); |
| | } |
| | } |
| |
|
| | jthread(const jthread&) = delete; |
| | jthread(jthread&&) noexcept = default; |
| | jthread& operator=(const jthread&) = delete; |
| |
|
| | jthread& operator=(jthread&& other) noexcept { |
| | m_thread.swap(other.m_thread); |
| | m_stop_state.swap(other.m_stop_state); |
| | return *this; |
| | } |
| |
|
| | void swap(jthread& other) noexcept { |
| | m_thread.swap(other.m_thread); |
| | m_stop_state.swap(other.m_stop_state); |
| | } |
| | [[nodiscard]] bool joinable() const noexcept { |
| | return m_thread.joinable(); |
| | } |
| | void join() { |
| | m_thread.join(); |
| | } |
| | void detach() { |
| | m_thread.detach(); |
| | m_stop_state.reset(); |
| | } |
| |
|
| | [[nodiscard]] id get_id() const noexcept { |
| | return m_thread.get_id(); |
| | } |
| | [[nodiscard]] native_handle_type native_handle() { |
| | return m_thread.native_handle(); |
| | } |
| | [[nodiscard]] stop_source get_stop_source() noexcept { |
| | return stop_source(m_stop_state); |
| | } |
| | [[nodiscard]] stop_token get_stop_token() const noexcept { |
| | return stop_source(m_stop_state).get_token(); |
| | } |
| | bool request_stop() noexcept { |
| | return get_stop_source().request_stop(); |
| | } |
| | [[nodiscard]] static unsigned int hardware_concurrency() noexcept { |
| | return thread::hardware_concurrency(); |
| | } |
| |
|
| | private: |
| | template <typename F, typename... Args> |
| | thread make_thread(F&& f, Args&&... args) { |
| | if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { |
| | return thread(std::move(f), get_stop_token(), std::move(args)...); |
| | } else { |
| | return thread(std::move(f), std::move(args)...); |
| | } |
| | } |
| |
|
| | shared_ptr<polyfill::stop_state> m_stop_state; |
| | thread m_thread; |
| | }; |
| |
|
| | } |
| |
|
| | namespace Common { |
| |
|
| | template <typename Condvar, typename Lock, typename Pred> |
| | void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { |
| | if (token.stop_requested()) { |
| | return; |
| | } |
| |
|
| | std::stop_callback callback(token, [&] { cv.notify_all(); }); |
| | cv.wait(lock, [&] { return pred() || token.stop_requested(); }); |
| | } |
| |
|
| | } |
| |
|
| | #endif |
| |
|