如何使用单个解锁方法(可称为读取器或写入器)实现C++读写器锁?

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

本文关键字:C++ 读写器 实现 读取 解锁 单个 何使用 方法      更新时间:2023-10-16

我正在做一个项目,它需要使用特定的操作系统抽象,我需要使用他们的信号量和互斥锁实现读写器锁。我目前有一个格式的设置:

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 方法来解锁编写器并释放所有读取器信号量插槽,或者只是释放读取器信号量插槽,但是,我正在努力,因为我想不出一个实现,它将在所有情况下都有效。如何在给定的设置中完成此操作?我知道这是可能的,因为 POSIX 能够在他们的实现中实现通用解锁方法,但我找不到任何关于如何完成的迹象,因此希望人们可以分享任何信息。

请注意,我不能使用 C++11 或其他操作系统原语。

好吧,定义两个函数UnlockReadUnlockWrite

我相信您不需要在同一时间在同一位置进行两次访问(写入/读取)。所以我建议的是另外两个类来锁定访问:

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在评论中坚持要有"一个"解锁 - 请考虑缺点:

假设它实现了对 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;
};

所以使用这个MultiThreadStack而不是 std::stack inReadWriteLock.

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

成功获取锁时,类型是已知的:要么运行多个读取器,要么只有一个写入器,则不能同时使用有效获取的锁运行读取器和写入器。

因此,当lock调用成功时,存储当前锁定模式就足够了,并且所有后续unlock调用(如果提供了读取许可,则可能很多,如果请求写入锁定,则实际上只有一个)都将属于该模式。