【问题标题】:How to implement a recursive MRSW lock?如何实现递归 MRSW 锁?
【发布时间】:2010-11-24 07:28:30
【问题描述】:

我的项目需要一个完全递归的多读取器/单写入器锁(共享互斥锁)——我不同意如果你有完整的 const 正确性就不需要它们的想法(有在 boost 邮件列表上对此进行了一些讨论),在我的情况下,锁应该保护一个完全透明的缓存,该缓存在任何情况下都是可变的。

至于递归 MRSW 锁的语义,我认为唯一有意义的是,在共享锁之外获取独占锁会暂时释放共享锁,当独占锁释放时重新获取。

有点奇怪的效果是解锁可以等待,但我可以忍受 - 无论如何编写很少发生,递归锁定通常只通过递归代码路径发生,在这种情况下,调用者必须准备好调用可能会在任何情况下等待案子。为了避免这种情况,人们仍然可以简单地升级锁而不是使用递归锁。

在独占锁之上获取共享锁显然只会增加锁计数。

所以问题就变成了——我应该如何实现它?带有临界区和两个信号量的常用方法在这里行不通,因为据我所知,唤醒的线程必须通过将其线程 ID 插入锁的所有者映射中来握手。

我想这可以通过两个条件变量和几个互斥锁来实现,但最终会使用的同步原语数量之多,对我来说听起来有点过多的开销。

我突然想到的一个想法是利用 TLS 来记住我持有的锁的类型(可能还有本地锁的计数)。必须考虑清楚 - 但我现在仍然会发布问题。

目标平台是 Win32,但这并不重要。请注意,我专门针对 Win2k,因此与 Windows 7 中新的 MRSW 锁定原语相关的任何内容都与我无关。 :-)

【问题讨论】:

    标签: c++ multithreading synchronization


    【解决方案1】:

    好的,我解决了。

    只需 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;
    

    现在,基本思路是这样的:

    解锁线程对mWriterWaitingmWriterActive 的完整更新由解锁线程执行。

    对于mWriterThreadIdmReaderActive,这是不可能的,因为等待线程在释放时需要插入自身。

    所以规则是,你永远不能访问这两个成员 except 来检查你是否持有读锁或者是当前的写入者 - 特别是它可能不用于检查是否有任何个读者/作者——为此你必须使用mReaderActiveThreadsmWriterActive(有点多余但必要)。

    我目前正在运行一些测试代码(这些代码已经发生了 30 分钟左右的死锁和无崩溃)——当我确定它是稳定的并且我已经稍微清理了代码时,我会把它放在一些粘贴箱上,并在此处的评论中添加一个链接(以防万一其他人需要这个)。

    【讨论】:

    • 忘了说mReaderActiveThreads显然也是由解锁线程管理的。
    • 你有没有让你的代码在任何地方都可用?你愿意吗?
    【解决方案2】:

    嗯,我做了一些思考。从简单的“两个信号量和一个临界区”开始,向结构中添加一个写入器锁计数和一个拥有写入器 TID。

    解锁仍然设置了关键秒中的大部分新状态。读取器通常仍会增加锁定计数 - 递归锁定只是将不存在的读取器添加到计数器。

    在 writer lock() 期间,我比较拥有 TID,如果 writer 已经拥有它,则增加写锁计数器。

    unlock() 无法设置新的 writer TID - 它不知道哪个会被唤醒,但如果 writer 在他们的 unlock() 中将其重置为零,这不是问题 - 当前线程 ID 永远不会为零,设置它是一个原子操作。

    一切听起来都很简单——还有一个令人讨厌的问题:编写者等待时的递归读取器-读取器锁会死锁。而且我不知道如何解决没有做一个读者偏向锁的问题......不知何故,我需要知道我是否已经拥有一个读者锁。

    在我意识到可用插槽的数量可能相当有限之后,使用 TLS 听起来不太好......

    【讨论】:

      【解决方案3】:

      据我了解,您需要为您的作者提供对数据的独占访问权限,而读者可以同时操作(如果这不是您想要的,请澄清您的问题)。

      我认为您需要实现一种“反向信号量”,即当信号量为正时将阻塞线程,并在为零时向所有等待线程发出信号的信号量。如果你这样做,你可以为你的程序使用两个这样的信号量。您的线程的操作可能如下:

      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(结合互斥量、计数器和条件变量)来实现此类信号量的方法。

      【讨论】:

        猜你喜欢
        • 2019-05-19
        • 1970-01-01
        • 2011-06-16
        • 1970-01-01
        • 1970-01-01
        • 2012-01-04
        • 2017-01-31
        • 2010-09-16
        • 2020-07-21
        相关资源
        最近更新 更多