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++

// -*- 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