shared_mutex (4906B)
1 // -*- c++ -*- 2 3 #include <condition_variable> 4 #include <mutex> 5 #include <system_error> 6 7 #define MUTEX_ASSERT(x) //assert(x) 8 //#include <cassert> 9 10 namespace std 11 { 12 13 // https://stackoverflow.com/questions/8635963/read-write-lock-implementation-using-mutex-only 14 class shared_mutex 15 { 16 public: 17 shared_mutex() = default; 18 shared_mutex(const shared_mutex&) = delete; 19 void operator=(const shared_mutex&) = delete; 20 21 void lock() 22 { 23 std::unique_lock l{mtx}; 24 ++wait_wr; 25 while (no != 0) 26 { 27 if (no == HAS_WR) // recursive locking 28 throw std::system_error(std::make_error_code( 29 std::errc::resource_deadlock_would_occur)); 30 wr.wait(l); 31 } 32 --wait_wr; 33 no = HAS_WR; 34 } 35 36 bool try_lock() 37 { 38 std::unique_lock l{mtx, std::try_to_lock}; 39 if (!l || no != 0) return false; 40 no = HAS_WR; 41 return true; 42 } 43 44 void unlock() // noexcept 45 { 46 std::unique_lock l{mtx}; 47 MUTEX_ASSERT(no == HAS_WR); 48 no = 0; 49 if (wait_wr) wr.notify_one(); 50 else if (wait_rd) rd.notify_all(); 51 } 52 53 void lock_shared() 54 { 55 std::unique_lock l{mtx}; 56 ++wait_rd; 57 rd.wait(l, [this]() { return no != HAS_WR; }); 58 --wait_rd; 59 ++no; 60 } 61 62 bool try_lock_shared() 63 { 64 std::unique_lock l{mtx, std::try_to_lock}; 65 if (!l || no == HAS_WR) return false; 66 ++no; 67 return true; 68 } 69 70 void unlock_shared() // noexcept 71 { 72 std::unique_lock l{mtx}; 73 MUTEX_ASSERT(no); 74 MUTEX_ASSERT(no != HAS_WR); 75 --no; 76 if (no == 0 && wait_wr) wr.notify_one(); 77 } 78 79 private: 80 mutex mtx; 81 condition_variable rd, wr; 82 unsigned no = 0, wait_rd = 0, wait_wr = 0; 83 static inline constexpr const unsigned HAS_WR = -1; 84 }; 85 86 template <typename Mutex> 87 class shared_lock 88 { 89 public: 90 using mutex_type = Mutex; 91 shared_lock() noexcept = default; 92 shared_lock(shared_lock&& o) noexcept : m{o.m}, own{o.own} 93 { 94 o.m = nullptr; 95 o.own = false; 96 } 97 explicit shared_lock(Mutex& m) : m{&m}, own{true} { m.lock_shared(); } 98 shared_lock(Mutex& m, std::defer_lock_t) noexcept : m{&m}, own{false} {} 99 shared_lock(Mutex& m, std::try_to_lock_t) : m{&m}, own{m.try_lock_shared()} {} 100 shared_lock(Mutex& m, std::adopt_lock_t) : m{&m}, own{true} {} 101 102 template <typename Rep, typename Period> 103 shared_lock(Mutex& m, const std::chrono::duration<Rep, Period>& timeout) 104 : m{&m}, own{true} 105 { m.try_lock_shared_for(timeout); } 106 template <typename Clock, typename Duration> 107 shared_lock(Mutex& m, const std::chrono::time_point<Clock, Duration>& time) 108 : m{&m}, own{true} 109 { m.try_lock_shared_until(time); } 110 111 ~shared_lock() noexcept { if (m && own) m->unlock_shared(); } 112 113 shared_lock& operator=(shared_lock&& o) noexcept 114 { 115 if (m && own) m->unlock_shared(); 116 m = o.m; own = o.own; 117 o.m = nullptr; o.own = false; 118 } 119 120 void lock() 121 { 122 CheckLockable(); 123 m->lock_shared(); 124 own = true; 125 } 126 127 bool try_lock() 128 { 129 CheckLockable(); 130 return own = m->try_lock_shared(); 131 } 132 133 template <typename Rep, typename Period> 134 bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout) 135 { 136 CheckLockable(); 137 return own = m->try_lcok_shared_for(timeout); 138 } 139 140 template <typename Clock, typename Duration> 141 bool try_lock_for(const std::chrono::time_point<Clock, Duration>& time) 142 { 143 CheckLockable(); 144 return own = m->try_lcok_shared_until(time); 145 } 146 147 void unlock() 148 { 149 // todo: what the hell am I supposed to throw when !own? 150 if (!m || !own) 151 throw std::system_error(std::make_error_code( 152 std::errc::operation_not_permitted)); 153 m->unlock_shared(); 154 } 155 156 // cppreference says it's a template, wtf? it shouldn't be 157 // https://timsong-cpp.github.io/cppwp/n4659/thread.lock.shared 158 void swap(shared_lock& o) noexcept 159 { 160 swap(m, o.m); 161 swap(own, o.own); 162 } 163 164 Mutex* release() noexcept 165 { 166 auto res = m; 167 m = nullptr; 168 own = false; 169 return res; 170 } 171 172 Mutex& mutex() const noexcept { return m; } 173 bool owns_lock() const noexcept { return own; } 174 explicit operator bool() const noexcept { return own; } 175 176 private: 177 void CheckLockable() 178 { 179 if (!m) 180 throw std::system_error(std::make_error_code( 181 std::errc::operation_not_permitted)); 182 if (own) 183 throw std::system_error(std::make_error_code( 184 std::errc::resource_deadlock_would_occur)); 185 } 186 187 Mutex* m = nullptr; 188 bool own = false; 189 }; 190 191 template <typename Mutex> 192 void swap(shared_lock<Mutex>& a, shared_lock<Mutex>& b) noexcept 193 { a.swap(b); } 194 195 } 196 197 #undef MUTEX_ASSERT