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
 |