ReaderWriterLockSlim.EnterUpgradeableReadLock()始终是死锁?

时间:2014-01-28 16:17:56

标签: c# multithreading thread-safety readerwriterlockslim

我对ReaderWriterLockSlim非常熟悉,但我最近尝试在类中实现EnterUpgradeableReadLock() ...我意识到当两个或多个线程运行时几乎可以肯定这肯定是一个保证死锁代码:

Thread A --> enter upgradeable read lock
Thread B --> enter upgradeable read lock
Thread A --> tries to enter write lock, blocks for B to leave read
Thread B --> tries to enter write lock, blocks for A to leave read
Thread A --> waiting for B to exit read lock
Thread B --> waiting for A to exit read lock

我在这里缺少什么?

修改

添加了我的方案的代码示例。 Run()方法将由两个或多个线程同时调用。

public class Deadlocker
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

    public void Run()
    {
        _lock.EnterUpgradeableReadLock();
        try
        {
            _lock.EnterWriteLock();
            try
            {
                // Do something
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }
        finally
        {
            _lock.ExitUpgradeableReadLock();
        }
    }
}

3 个答案:

答案 0 :(得分:28)

OP之后很长一段时间,但我不同意目前接受的答案。

声明Thread B --> enter upgradeable read lock不正确。来自the docs

  

任何时候只有一个线程可以处于可升级模式

并回应您的意见:它旨在用于读写模式的非常不同的用法。

<强> TL; DR 即可。可升级模式非常有用:

  • 如果作者必须在写入之前检查共享资源,并且(可选)需要避免与其他作者的竞争条件;
  • 并且它不应该阻止读者,直到它100%确定它必须写到共享资源;
  • 一旦执行了检查,作者就很可能决定不应该将写入共享资源。

或者,在伪代码中,这是:

// no other writers or upgradeables allowed in here => no race conditions
EnterUpgradeableLock(); 
if (isWriteRequired()) { EnterWriteLock(); DoWrite(); ExitWriteLock(); } 
ExitUpgradeableLock();

给出“更好的表现” ÷ 而不是:

EnterWriteLock(); if (isWriteRequired()) { DoWrite(); } ExitWriteLock();

如果由于SpinLock使用独占锁定部分需要很长时间,则应谨慎使用。


类似的锁定构造

可升级锁与SQL服务器SIX lock(与Intent共享以获得独立性)令人惊讶地类似。

  • 要用这些术语重写上面的语句,可升级锁定说“作者打算写入资源,但希望与其他读者分享,同时[双]检查条件,看它是否应该立即锁定和执行写“

如果没有Intent锁,您必须在eXclusive锁中执行“我是否应该进行此更改”检查,这会损害并发性。

为什么你不能共享可升级?

如果可升级锁可与其他可升级锁共享,则可能与其他可升级锁拥有者具有竞争条件。因此,您需要在写锁定内部进行一次检查,从而消除了检查的好处,而不会阻止其他读取。

实施例

如果我们将所有锁定等待/进入/退出事件视为顺序,并将锁定内的工作视为并行,那么我们可以用“大理石”形式编写方案(e输入; w等待; x退出; cr检查资源; mr mutate资源; R共享/读取; U意图/可升级; W eXclusive /写):

1--eU--cr--wW----eW--mr--xWxU--------------
2------eR----xR----------------eR--xR------
3--------eR----xR--------------------------
4----wU----------------------eU--cr--xU----

单词:T1进入Upgradeable / Intent锁。 T4等待Upgradeable / Intent锁定。 T2和T3输入读锁定。 T1同时检查资源,赢得比赛并等待eXclusive / Write锁定。 T2&amp; T3退出锁。 T1进入eXclusive / Write锁定并进行更改。 T4进入升级/意图锁定,不需要进行更改并退出,而不会阻止T2同时执行另一次读取。

在8个子弹点......

可升级锁是:

  1. 任何作家使用;
  2. 可能先检查然后决定不以任何理由执行写入(丢失竞争条件或Getsert模式);
  3. 谁不应该阻止读者,直到它知道它必须执行写;
  4. 因此它将取出一个独占锁并这样做。
  5. 如果下列之一适用(包括但不限于):

    ,则无需升级
    1. writelock-check-nowrite-exit近似为零的读者和作者之间的争用率(写入条件检查超快) - 即可升级的构造无助于读者吞吐量;
    2. 写入一次写入锁定的写入器的概率是〜1,因为:

      • ReadLock-Check-WriteLock-DoubleCheck如此之快,只会导致每万亿次写入失败者;
      • 所有更改都是唯一的(所有更改都必须发生,比赛不能存在);
      • “最后一次更改获胜”(所有更改仍然必须发生,即使它们不是唯一的)
    3. 如果lock(...){...}更合适,也不是必需的,即:

      1. 读取和/或写入窗口重叠的概率很低(锁定可以防止非常罕见的事件,如保护极有可能的事件,而不是简单的内存屏障要求)
      2. 您所有的锁定获取都是可升级或写入,永远不会读取('duh')
      3. ÷ “性能”取决于您的定义

        如果您将锁定对象视为表格,并将受保护资源视为层次结构中较低的资源,则此类比约为

        读取锁定中的初始检查是可选的,可升级锁定内的检查是必需的,因此可以单一或双重检查模式使用。

答案 1 :(得分:1)

答案 2 :(得分:-1)

您的示例中有错误

private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

应该是

private static readonly ReaderWriterLockSlim _lock = new  ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

现在,每当一个类被实例化时,你的代码就会创建一个新的ReaderWriterLockSlim实例,它无法锁定任何东西,因为每个单独的线程都拥有它自己的实例。将其设置为静态将强制所有线程使用一个将按原样运行的实例

相关问题