如何使用单个解锁方法实现 C++ Reader-Writer 锁,它可以称为 reader 或 writer?

How can I implement a C++ Reader-Writer lock using a single unlock method, which can be called be a reader or writer?

我正在做一个项目,它需要使用特定的 OS 抽象,我需要使用它们的信号量和互斥锁来实现 reader-writer 锁。我目前的设置格式为:

class ReadWriteLock
{
public:
   ReadWriteLock(uint32_t maxReaders);
   ~ReadWriteLock();
   uint32_t GetMaxReaders() const;
   eResult  GetReadLock(int32_t timeout);
   eResult  GetWriteLock(int32_t timeout);
   eResult  Unlock();

private:
   uint32_t m_MaxReaders;
   Mutex* m_WriterMutex;
   Semaphore* m_ReaderSemaphore;

};

在这个实现中,我需要使用这个 Unlock 方法来解锁写入器并释放所有 reader 信号量槽,或者简单地释放一个 reader 信号量槽,但是,我正在努力我想不出一个在所有情况下都适用的实现。我怎样才能在给定的设置中完成这项工作?我知道这是可能的,因为 POSIX 能够在他们的实现中实现一个通用的解锁方法,但我找不到任何关于如何完成的指示,所以希望人们能分享任何信息。

请注意,我不能使用 C++11 或其他 OS 原语。

好吧,定义两个函数UnlockReadUnlockWrite

我相信您不需要在同一个地方同时访问这两个 (Write/Read)。所以我的建议是另外两个 类 用于锁定访问:

class ReadWriteAccess
{
public:
   ReadWriteAccess(uint32_t maxReaders);
   ~ReadWriteAccess();
   uint32_t GetMaxReaders() const;
   uint32_t GetMaxReaders() const;
   eResult  GetReadLock(int32_t timeout);
   eResult  GetWriteLock(int32_t timeout);
   eResult  UnlockWrite();
   eResult  UnlockRead();

private:
   uint32_t m_MaxReaders;
   Mutex* m_WriterMutex;
   Semaphore* m_ReaderSemaphore;

};

并且有单独的 类 用于读写锁并使用 RAII 以确保安全:

class ReadLock
{
public:
    ReadLock(ReadWriteAccess& access, int32_t timeout) : access(access) 
    {
        result = access.GetReadLock(timeout);
    }
    eResult getResult() const { return result; }
    ~ReadLock()
    {
        if (result)
            access.UnlockRead();
    }
private:
    ReadWriteAccess& access;
    eResult  result;
};

并像这样使用:

T someResource;
ReadWriteAccess someResourceGuard;

void someFunction()
{
    ReadLock lock(someResourceGuard);
    if (lock.getResult())
       cout << someResource; // it is safe to read something from resource
}

当然,您可以轻松地自己编写非常相似的实现 WriteLock


由于OP在评论中坚持要"one"解锁-请考虑缺点:

假设它实现了某种最后调用 Lock 函数的堆栈:

class ReadWriteLock
{
public:
   ReadWriteLock(uint32_t maxReaders);
   ~ReadWriteLock();
   uint32_t GetMaxReaders() const;
   eResult  GetReadLock(int32_t timeout)
   {
       eResult result = GetReadLockImpl(timestamp);
       if (result)
           lockStack.push(READ);
   }
   eResult  GetWriteLock(int32_t timeout)
   {
       eResult result = GetWriteLockImpl(timestamp);
       if (result)
           lockStack.push(WRITE);
   }
   eResult  Unlock()
   {
       LastLockMode lockMode = lockStack.top();
       lockStack.pop();
       if (lockMode == READ) 
           UnlockReadImpl();
       else
           UnlockWriteImpl();
   }

private:
   uint32_t m_MaxReaders;
   Mutex* m_WriterMutex;
   Semaphore* m_ReaderSemaphore;

    enum Mode { READ, WRITE };
    std::stack<Mode> lockStack;
};

但以上仅适用于单线程应用程序。单线程应用程序永远不需要任何锁。

所以 - 你必须有多线程堆栈 - 如:

template <typename Value>
class MultiThreadStack
{
public:
    void push(Value)
    {
       stackPerThread[getThreadId()].push(value);
    }
    Value top()
    {
       return stackPerThread[getThreadId()].top();
    }
    void pop()
    {
       stackPerThread[getThreadId()].pop();
    }
private:
    ThreadId getThreadId() { return /* your system way to get thread id*/; }
    std::map<ThreadId, std::stack<Value>> stackPerThread;
};

所以在 ReadWriteLock.

中使用这个 MultiThreadStack 而不是 std::stack

但是,上面的 std::map 需要 ReadWriteLock 来锁定从多个线程访问它 - 所以,好吧,要么你知道你所有的线程在你开始使用这些东西之前(预注册),否则你最终会遇到与 here 描述的相同的问题。所以我的建议 - 如果可以的话 - 改变你的设计。

当成功获取锁时,类型是已知的:要么你有很多读者运行,要么只有一个写者,你不能同时拥有读者和写者运行一个有效获取的锁。

因此,当 lock 调用成功以及所有后续 unlock 调用时,存储当前锁定模式就足够了(如果提供了读取许可,则可能有很多,如果请求写入锁定,则只有一个) 将是那种模式。