You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
4.8 KiB
C++
198 lines
4.8 KiB
C++
// -*- c++ -*-
|
|
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <system_error>
|
|
|
|
#define MUTEX_ASSERT(x) //assert(x)
|
|
//#include <cassert>
|
|
|
|
namespace std
|
|
{
|
|
|
|
// https://stackoverflow.com/questions/8635963/read-write-lock-implementation-using-mutex-only
|
|
class shared_mutex
|
|
{
|
|
public:
|
|
shared_mutex() = default;
|
|
shared_mutex(const shared_mutex&) = delete;
|
|
void operator=(const shared_mutex&) = delete;
|
|
|
|
void lock()
|
|
{
|
|
std::unique_lock l{mtx};
|
|
++wait_wr;
|
|
while (no != 0)
|
|
{
|
|
if (no == HAS_WR) // recursive locking
|
|
throw std::system_error(std::make_error_code(
|
|
std::errc::resource_deadlock_would_occur));
|
|
wr.wait(l);
|
|
}
|
|
--wait_wr;
|
|
no = HAS_WR;
|
|
}
|
|
|
|
bool try_lock()
|
|
{
|
|
std::unique_lock l{mtx, std::try_to_lock};
|
|
if (!l || no != 0) return false;
|
|
no = HAS_WR;
|
|
return true;
|
|
}
|
|
|
|
void unlock() // noexcept
|
|
{
|
|
std::unique_lock l{mtx};
|
|
MUTEX_ASSERT(no == HAS_WR);
|
|
no = 0;
|
|
if (wait_wr) wr.notify_one();
|
|
else if (wait_rd) rd.notify_all();
|
|
}
|
|
|
|
void lock_shared()
|
|
{
|
|
std::unique_lock l{mtx};
|
|
++wait_rd;
|
|
rd.wait(l, [this]() { return no != HAS_WR; });
|
|
--wait_rd;
|
|
++no;
|
|
}
|
|
|
|
bool try_lock_shared()
|
|
{
|
|
std::unique_lock l{mtx, std::try_to_lock};
|
|
if (!l || no == HAS_WR) return false;
|
|
++no;
|
|
return true;
|
|
}
|
|
|
|
void unlock_shared() // noexcept
|
|
{
|
|
std::unique_lock l{mtx};
|
|
MUTEX_ASSERT(no);
|
|
MUTEX_ASSERT(no != HAS_WR);
|
|
--no;
|
|
if (no == 0 && wait_wr) wr.notify_one();
|
|
}
|
|
|
|
private:
|
|
mutex mtx;
|
|
condition_variable rd, wr;
|
|
unsigned no = 0, wait_rd = 0, wait_wr = 0;
|
|
static inline constexpr const unsigned HAS_WR = -1;
|
|
};
|
|
|
|
template <typename Mutex>
|
|
class shared_lock
|
|
{
|
|
public:
|
|
using mutex_type = Mutex;
|
|
shared_lock() noexcept = default;
|
|
shared_lock(shared_lock&& o) noexcept : m{o.m}, own{o.own}
|
|
{
|
|
o.m = nullptr;
|
|
o.own = false;
|
|
}
|
|
explicit shared_lock(Mutex& m) : m{&m}, own{true} { m.lock_shared(); }
|
|
shared_lock(Mutex& m, std::defer_lock_t) noexcept : m{&m}, own{false} {}
|
|
shared_lock(Mutex& m, std::try_to_lock_t) : m{&m}, own{m.try_lock_shared()} {}
|
|
shared_lock(Mutex& m, std::adopt_lock_t) : m{&m}, own{true} {}
|
|
|
|
template <typename Rep, typename Period>
|
|
shared_lock(Mutex& m, const std::chrono::duration<Rep, Period>& timeout)
|
|
: m{&m}, own{true}
|
|
{ m.try_lock_shared_for(timeout); }
|
|
template <typename Clock, typename Duration>
|
|
shared_lock(Mutex& m, const std::chrono::time_point<Clock, Duration>& time)
|
|
: m{&m}, own{true}
|
|
{ m.try_lock_shared_until(time); }
|
|
|
|
~shared_lock() noexcept { if (m && own) m->unlock_shared(); }
|
|
|
|
shared_lock& operator=(shared_lock&& o) noexcept
|
|
{
|
|
if (m && own) m->unlock_shared();
|
|
m = o.m; own = o.own;
|
|
o.m = nullptr; o.own = false;
|
|
}
|
|
|
|
void lock()
|
|
{
|
|
CheckLockable();
|
|
m->lock_shared();
|
|
own = true;
|
|
}
|
|
|
|
bool try_lock()
|
|
{
|
|
CheckLockable();
|
|
return own = m->try_lock_shared();
|
|
}
|
|
|
|
template <typename Rep, typename Period>
|
|
bool try_lock_for(const std::chrono::duration<Rep, Period>& timeout)
|
|
{
|
|
CheckLockable();
|
|
return own = m->try_lcok_shared_for(timeout);
|
|
}
|
|
|
|
template <typename Clock, typename Duration>
|
|
bool try_lock_for(const std::chrono::time_point<Clock, Duration>& time)
|
|
{
|
|
CheckLockable();
|
|
return own = m->try_lcok_shared_until(time);
|
|
}
|
|
|
|
void unlock()
|
|
{
|
|
// todo: what the hell am I supposed to throw when !own?
|
|
if (!m || !own)
|
|
throw std::system_error(std::make_error_code(
|
|
std::errc::operation_not_permitted));
|
|
m->unlock_shared();
|
|
}
|
|
|
|
// cppreference says it's a template, wtf? it shouldn't be
|
|
// https://timsong-cpp.github.io/cppwp/n4659/thread.lock.shared
|
|
void swap(shared_lock& o) noexcept
|
|
{
|
|
swap(m, o.m);
|
|
swap(own, o.own);
|
|
}
|
|
|
|
Mutex* release() noexcept
|
|
{
|
|
auto res = m;
|
|
m = nullptr;
|
|
own = false;
|
|
return res;
|
|
}
|
|
|
|
Mutex& mutex() const noexcept { return m; }
|
|
bool owns_lock() const noexcept { return own; }
|
|
explicit operator bool() const noexcept { return own; }
|
|
|
|
private:
|
|
void CheckLockable()
|
|
{
|
|
if (!m)
|
|
throw std::system_error(std::make_error_code(
|
|
std::errc::operation_not_permitted));
|
|
if (own)
|
|
throw std::system_error(std::make_error_code(
|
|
std::errc::resource_deadlock_would_occur));
|
|
}
|
|
|
|
Mutex* m = nullptr;
|
|
bool own = false;
|
|
};
|
|
|
|
template <typename Mutex>
|
|
void swap(shared_lock<Mutex>& a, shared_lock<Mutex>& b) noexcept
|
|
{ a.swap(b); }
|
|
|
|
}
|
|
|
|
#undef MUTEX_ASSERT
|