ReaderWriterLockSlim.EnterUpgradeableReadLock()始终是死锁?
我对ReaderWriterLockSlim
非常熟悉,但我最近尝试在类中实现了EnterUpgradeableReadLock()
…很快我意识到当2个或更multithreading运行代码时,这几乎肯定是一个保证死锁:
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(); } } }
OP之后很长一段时间,但我不同意目前接受的答案。
语句Thread B --> enter upgradeable read lock
不正确。 来自文档
任何时候只有一个线程可以处于可升级模式
并回应您的意见:它旨在用于读写模式的非常不同的用法。
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锁 (与Intent共享以获得eXclusive)惊人相似† 。
- 要用这些术语重写上面的语句,可升级锁定说“作者打算写入资源,但希望与其他读者共享,同时[双]检查条件,看它是否应该独立锁定并执行写入“ ‡ 。
如果没有Intent锁,则必须在eXclusive锁中执行“我应该进行此更改”检查,这会损害并发性。
为什么你不能共享可升级?
如果可升级锁可与其他可升级锁共享,则可能与其他可升级锁所有者具有竞争条件。 因此,您需要在Write锁定内部进行一次检查,从而消除了检查的好处,而不会阻止其他读取操作。
例
如果我们将所有锁定等待/进入/退出事件视为顺序,并且锁定内部的工作是并行的,那么我们可以用“Marble”forms编写一个场景( e
enter; w
wait; x
exit; cr
check resource; mr
mutate资源; R
共享/读取; U
意图/可升级;包含/写入):
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和T3退出锁。 T1进入eXclusive / Write锁定并进行更改。 T4进入可升级/意图锁定,不需要进行更改并退出,而不会阻止T2同时执行另一次读取。
在8个子弹点……
可升级锁是:
- 任何作家使用;
- 谁可能先检查然后决定不以任何理由执行写入(丢失竞争条件,或以Getsert模式);
- 谁不应该阻止读者,直到它知道它必须执行写;
- 因此它将取出一个独占锁并这样做。
如果下列之一适用(包括但不限于),则不需要升级:
-
writelock-check-nowrite-exit
读者和作者之间的争用率大约为零(写入条件检查超快) – 即可升级的构造对读者吞吐量没有帮助; -
在Write锁中写入一次的写入器的概率是〜1,因为:
-
ReadLock-Check-WriteLock-DoubleCheck
是如此之快,它只会导致每万亿次写入失败者; - 所有变化都是独一无二的(所有变化都必须发生,种族不可能存在); 要么
- “最后的改变胜利”(所有变化仍然必须发生,即使它们不是唯一的)
-
如果lock(...){...}
更合适,也不需要它,即:
- 重叠读取和/或写入窗口的概率很低(锁定可以与防止非常罕见的事件一样多,因为保护极有可能的事件,更不用说简单的内存屏障要求)
- 您所有的锁定都是可升级或写入,永远不会读取(’duh’)
÷ “性能”取决于您的定义
† 如果您将锁定对象视为表,并将受保护资源视为层次结构中较低的资源,则此类比近似成立
‡ 读取锁定中的初始检查是可选的,可升级锁定内的检查是强制性的,因此可以单个或双重检查模式使用。
我建议避免使用EnterUpgradeableReadLock()。 只需使用EnterWriteLock()即可。 我知道这似乎效率低下,可升级的读锁几乎和写锁一样糟糕。
- http://ayende.com/blog/4349/using-readerwriterlockslims-enterupgradeablereadlock
- http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas
- http://ayende.com/blog/4349/using-readerwriterlockslims-enterupgradeablereadlock
- ReaderWriterLockSlim.EnterUpgradeableReadLock()与Monitor.Enter()基本相同吗?
您的示例中有错误
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
它应该是
private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
现在在代码中,每次实例化类时,它都会创建新的ReaderWriterLockSlim实例,它无法锁定任何内容,因为每个线程都有自己的实例。 使其静态将强制所有线程使用一个将按预期工作的实例