我的项目需要一个完全递归的多读者/单作者锁(共享互斥锁) - 我不同意这样的观点:如果你有完全的const-correctness,你不应该需要它们(有关于boost邮件列表上的一些讨论,在我的情况下,锁应该保护一个完全透明的缓存,这在任何情况下都是可变的。
至于递归MRSW锁的语义,我认为唯一有意义的是除了共享锁之外获取一个独占锁暂时释放共享锁,在独占锁被释放时重新获取。
有一些奇怪的效果,解锁可以等待,但我可以忍受 - 无论如何写很少发生和递归锁定通常只通过递归代码路径发生,在这种情况下,调用者必须准备调用可能在任何等待案件。为了避免这种情况,人们仍然可以简单地升级锁而不是使用递归锁定。
在独占锁上获取共享锁显然只会增加锁定数。
所以问题就变成了 - 我应该如何实现呢?关键部分和两个信号量的常用方法在这里不起作用,因为 - 据我所知 - 唤醒线程必须通过将其线程ID插入锁的所有者地图来握手。
我认为使用两个条件变量和一些互斥量是可行的,但是最终会使用大量的同步原语听起来有点太多我的口味。
我想到的一个想法是利用TLS来记住我持有的锁类型(可能还有本地锁定计数)。必须考虑一下 - 但我现在仍然会发布这个问题。
目标平台是Win32,但这应该不重要。请注意,我专门针对Win2k,因此与Windows 7中新的MRSW锁原语相关的任何内容都与我无关。 : - )
答案 0 :(得分:3)
好的,我解决了。
只需2个信号量,一个关键部分,几乎没有比常规非递归MRSW锁定更多的锁定(显然在锁内部花费了更多的CPU时间,因为必须管理多重映射) - 但它棘手。我想出的结构看起来像这样:
// Protects everything that follows, except mWriterThreadId and mRecursiveUpgrade
CRITICAL_SECTION mLock;
// Semaphore to wait on for a read lock
HANDLE mSemaReader;
// Semaphore to wait on for a write lock
HANDLE mSemaWriter;
// Number of threads waiting for a write lock.
int mWriterWaiting;
// Number of times the writer entered the write lock.
int mWriterActive;
// Number of threads inside a read lock. Note that this does not include
// recursive read locks.
int mReaderActiveThreads;
// Whether or not the current writer obtained the lock by a recursive
// upgrade. Note that this member might be set outside the critical
// section, so it should only be read from by the writer during his
// unlock.
bool mRecursiveUpgrade;
// This member contains the current thread id once for each
// (recursive) read lock held by the current thread in addition to an
// undefined number of other thread ids which may or may not hold a
// read lock, even inside the critical section (!).
std::multiset<unsigned long> mReaderActive;
// If there is no writer this member contains 0.
// If the current thread is the writer this member contains his
// thread-id.
// Otherwise it can contain either of them, even inside the
// critical section (!).
// Also note that it might be set outside the critical section.
unsigned long mWriterThreadId;
现在,基本思路是:
解锁线程执行解锁的mWriterWaiting
和mWriterActive
的完全更新。
对于mWriterThreadId
和mReaderActive
,这是不可能的,因为等待线程在释放时需要自行插入。
所以规则是,您可能永远不会访问除之外的那两个成员来检查您是持有读锁还是当前作者 - 特别是它可能不会用于检查是否是有任何读者/作者 - 因为你必须使用(有些多余但因此必要)mReaderActiveThreads
和mWriterActive
。
我目前正在运行一些测试代码(已经发生了死锁 - 并且没有崩溃30分钟左右) - 当我确定它是稳定的并且我已经清理了一些代码我会把它放在一些pastebin上并在评论中添加一个链接(以防万一其他人需要这个)。
答案 1 :(得分:0)
好吧,我做了一些思考。从简单的“两个信号量和一个关键部分”开始,可以在结构中添加写入器锁定计数和拥有写入器TID。
解锁仍然会在暴击中设置大部分新状态。读者仍然通常会增加锁定计数 - 递归锁定只是将一个不存在的读者添加到计数器中。
在writers lock()期间,我比较拥有的TID,如果编写器已经拥有它,则写锁定计数器会增加。
解锁()无法设置新的编写器TID - 它不知道哪一个会被唤醒,但是如果编写者在解锁时将其重置为零()则不是问题 - 当前线程id永远不会为零,并将其设置为原子操作。
所有听起来都很简单 - 一个令人讨厌的问题:在写作者等待时递归的读者 - 读者锁将会死锁。而且我不知道如何解决读者偏向锁定的问题...不知怎的,我需要知道我是否已经拥有读卡器锁。
在我意识到可用插槽的数量可能相当有限之后,使用TLS听起来并不太好......
答案 2 :(得分:0)
据我了解,您需要为作者提供对数据的独占访问权限,而读者可以同时操作(如果这不是您想要的,请澄清您的问题)。
我认为你需要实现一种“反向信号量”,即在正数时阻塞线程的信号量,并在零时发出所有等待线程的信号。如果这样做,您可以为您的程序使用两个这样的信号量。您的线程的操作可能如下:
Reader:
(1) wait on sem A
(2) increase sem B
(3) read operation
(4) decrease sem B
Writer:
(1) increase sem A
(2) wait on sem B
(3) write operation
(4) decrease sem A
通过这种方式,只要所有挂起的读者都已完成阅读,作者就会执行写操作。一旦您的作家完成,读者就可以在不相互阻挡的情况下恢复操作。
我不熟悉Windows互斥/信号量工具,但我可以想到一种使用POSIX线程API(组合互斥锁,计数器和条件变量)来实现这种信号量的方法。