From aee4958549959e67457dba81b0944a11c641fcfe Mon Sep 17 00:00:00 2001 From: yoh Date: Mon, 23 Jan 2017 10:11:54 +0900 Subject: [PATCH] add yamc::fair::{timed_mutex, recursive_timed_mutex} --- README.md | 4 +- include/fair_mutex.hpp | 157 ++++++++++++++++++++++++++++++++++++---- tests/basic_test.cpp | 6 +- tests/compile_test.cpp | 4 + tests/dump_sizeof.cpp | 2 + tests/fairness_test.cpp | 4 +- 6 files changed, 158 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c8b7c4e..30e4835 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Note: [`std::mutex`'s default constructor][mutex_ctor] is constexpr, but `yamc:: - `yamc::checked::recursive_timed_mutex`: requirements debugging, recursive, support timeout - `yamc::fair::mutex`: fairness, non-recursive - `yamc::fair::recursive_mutex`: fairness, recursive +- `yamc::fair::timed_mutex`: fairness, non-recursive, support timeout +- `yamc::fair::recursive_timed_mutex`: fairness, recursive, support timeout - `yamc::alternate::recursive_mutex`: recursive C++11/14/17 Standard Library define variable mutex types: @@ -43,7 +45,7 @@ C++11/14/17 Standard Library define variable mutex types: # Tweaks -## Busy waiting for spinlock mutex +## Busy waiting in spinlock mutex The spinlock mutexes use an exponential backoff algorithm in busy waiting to acquire lock as default. These backoff algorithm of spinlock `mutex`-es are implemented with policy-based template class `basic_mutex`. You can tweak the algorithm by specifying BackoffPolicy when you instantiate spinlock mutex type, or define the following macros to change default behavior of all spinlock mutex types. diff --git a/include/fair_mutex.hpp b/include/fair_mutex.hpp index 40cda5c..f0b75f6 100644 --- a/include/fair_mutex.hpp +++ b/include/fair_mutex.hpp @@ -26,6 +26,8 @@ #ifndef YAMC_FAIR_MUTEX_HPP_ #define YAMC_FAIR_MUTEX_HPP_ +#include +#include #include #include #include @@ -38,19 +40,15 @@ namespace yamc { */ namespace fair { -class mutex { +namespace detail { + +class mutex_base { +protected: std::size_t next_ = 0; std::size_t curr_ = 0; std::condition_variable cv_; std::mutex mtx_; -public: - mutex() = default; - ~mutex() = default; - - mutex(const mutex&) = delete; - mutex& operator=(const mutex&) = delete; - void lock() { std::unique_lock lk(mtx_); @@ -78,7 +76,8 @@ class mutex { }; -class recursive_mutex { +class recursive_mutex_base { +protected: std::size_t next_ = 0; std::size_t curr_ = 0; std::size_t ncount_ = 0; @@ -86,13 +85,6 @@ class recursive_mutex { std::condition_variable cv_; std::mutex mtx_; -public: - recursive_mutex() = default; - ~recursive_mutex() = default; - - recursive_mutex(const recursive_mutex&) = delete; - recursive_mutex& operator=(const recursive_mutex&) = delete; - void lock() { const auto tid = std::this_thread::get_id(); @@ -142,6 +134,139 @@ class recursive_mutex { } }; +} // namespace detail + + +class mutex : private detail::mutex_base { + using base = detail::mutex_base; + +public: + mutex() = default; + ~mutex() = default; + + mutex(const mutex&) = delete; + mutex& operator=(const mutex&) = delete; + + using base::lock; + using base::try_lock; + using base::unlock; +}; + + +class timed_mutex : private detail::mutex_base { + using base = detail::mutex_base; + + template + bool do_try_lockwait(const std::chrono::time_point& tp) + { + std::unique_lock lk(mtx_); + std::size_t request = next_; + while (request != curr_) { + if (cv_.wait_until(lk, tp) == std::cv_status::timeout) { + if (request == curr_) // re-check predicate + break; + return false; + } + } + ++next_; + return true; + } + +public: + timed_mutex() = default; + ~timed_mutex() = default; + + timed_mutex(const timed_mutex&) = delete; + timed_mutex& operator=(const timed_mutex&) = delete; + + using base::lock; + using base::try_lock; + using base::unlock; + + template + bool try_lock_for(const std::chrono::duration& duration) + { + const auto tp = std::chrono::system_clock::now() + duration; + return do_try_lockwait(tp); + } + + template + bool try_lock_until(const std::chrono::time_point& tp) + { + return do_try_lockwait(tp); + } +}; + + +class recursive_mutex : private detail::recursive_mutex_base { + using base = detail::recursive_mutex_base; + +public: + recursive_mutex() = default; + ~recursive_mutex() = default; + + recursive_mutex(const recursive_mutex&) = delete; + recursive_mutex& operator=(const recursive_mutex&) = delete; + + using base::lock; + using base::try_lock; + using base::unlock; +}; + + +class recursive_timed_mutex : private detail::recursive_mutex_base { + using base = detail::recursive_mutex_base; + + template + bool do_try_lockwait(const std::chrono::time_point& tp) + { + const auto tid = std::this_thread::get_id(); + std::unique_lock lk(mtx_); + if (owner_ == tid) { + assert(0 < ncount_); + ++ncount_; + return true; + } + std::size_t request = next_; + while (request != curr_) { + if (cv_.wait_until(lk, tp) == std::cv_status::timeout) { + if (request == curr_) // re-check predicate + break; + return false; + } + } + ++next_; + assert(ncount_ == 0 && owner_ == std::thread::id()); + ncount_ = 1; + owner_ = tid; + return true; + } + +public: + recursive_timed_mutex() = default; + ~recursive_timed_mutex() = default; + + recursive_timed_mutex(const recursive_timed_mutex&) = delete; + recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; + + using base::lock; + using base::try_lock; + using base::unlock; + + template + bool try_lock_for(const std::chrono::duration& duration) + { + const auto tp = std::chrono::system_clock::now() + duration; + return do_try_lockwait(tp); + } + + template + bool try_lock_until(const std::chrono::time_point& tp) + { + return do_try_lockwait(tp); + } +}; + } // namespace fair } // namespace yamc diff --git a/tests/basic_test.cpp b/tests/basic_test.cpp index d09fba5..662e67b 100644 --- a/tests/basic_test.cpp +++ b/tests/basic_test.cpp @@ -89,7 +89,9 @@ TYPED_TEST(NormalMutexTest, TryLockFail) using RecursiveMutexTypes = ::testing::Types< yamc::checked::recursive_mutex, + yamc::checked::recursive_timed_mutex, yamc::fair::recursive_mutex, + yamc::fair::recursive_timed_mutex, yamc::alternate::recursive_mutex >; @@ -182,7 +184,9 @@ TYPED_TEST(RecursiveMutexTest, TryLockFail) using TimedMutexTypes = ::testing::Types< yamc::checked::timed_mutex, - yamc::checked::recursive_timed_mutex + yamc::checked::recursive_timed_mutex, + yamc::fair::timed_mutex, + yamc::fair::recursive_timed_mutex >; template diff --git a/tests/compile_test.cpp b/tests/compile_test.cpp index 7010cbf..b93bafa 100644 --- a/tests/compile_test.cpp +++ b/tests/compile_test.cpp @@ -78,8 +78,12 @@ int main() test_requirements(); test_requirements_timed(); test_requirements_timed(); + test_requirements(); test_requirements(); + test_requirements_timed(); + test_requirements_timed(); + test_requirements(); return 0; } diff --git a/tests/dump_sizeof.cpp b/tests/dump_sizeof.cpp index ccbcce7..d79af7f 100644 --- a/tests/dump_sizeof.cpp +++ b/tests/dump_sizeof.cpp @@ -33,6 +33,8 @@ int main() DUMP(yamc::checked::recursive_timed_mutex); DUMP(yamc::fair::mutex); DUMP(yamc::fair::recursive_mutex); + DUMP(yamc::fair::timed_mutex); + DUMP(yamc::fair::recursive_timed_mutex); DUMP(yamc::alternate::recursive_mutex); return 0; } diff --git a/tests/fairness_test.cpp b/tests/fairness_test.cpp index 6bc5d0e..b7dc497 100644 --- a/tests/fairness_test.cpp +++ b/tests/fairness_test.cpp @@ -28,7 +28,9 @@ std::mutex g_guard; using FairMutexTypes = ::testing::Types< yamc::fair::mutex, - yamc::fair::recursive_mutex + yamc::fair::recursive_mutex, + yamc::fair::timed_mutex, + yamc::fair::recursive_timed_mutex >; template