问题描述
我很熟悉的 ReaderWriterLockSlim
但尝试我的手在最近的一类实施 EnterUpgradeableReadLock()
。 ..不久之后我意识到,这几乎可以肯定是有保证的僵局时,2个或多个线程运行的代码:
发A - - >进入升级读锁
线程B - >进入升级读锁
线程A - >试图进入写锁定,对于B嵌段离开读取
线程B - >试图进入写锁定,对于A块离开读取
线程A - >等待B退出读锁
线程B - >等待退出读锁
我缺少的是在这里吗?
修改
增加了代码,我的方案的一个例子。在运行()
的方法将通过2个或多个线程同时被调用。
公共类Deadlocker
{
私人只读ReaderWriterLockSlim _lock =新的ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
公共无效的run()
{
_lock.EnterUpgradeableReadLock();
试
{
_lock.EnterWriteLock();
试
{
//做些什么
}
终于
{
_lock.ExitWriteLock();
}
}
终于
{
_lock.ExitUpgradeableReadLock();
}
}
}
的的OP后,很长一段时间,但我不与目前公认的答案达成一致。的
语句线程B - >进入升级读锁
不正确。从
而在响应您的意见:它针对的是非常不同的使用情况,以读写模式
TL; DR 。升级模式非常有用:
- 如果一个作家必须写入之前检查共享资源和(可选)需要避免竞争条件,其他作家;
- ,它不应该阻止读者的,直到它是100%肯定它必须写的共享资源;
- 和它很可能是作家将决定它不应该写应用于一旦执行检查的共享资源。
或者,在伪代码,其中这样的:
//无其他作家还是允许在这里upgradeables =>没有竞争条件
EnterUpgradeableLock();
如果(isWriteRequired()){EnterWriteLock(); DoWrite(); ExitWriteLock(); }
ExitUpgradeableLock();
给更好性能比这样的:
EnterWriteLock();如果(isWriteRequired()){DoWrite(); } ExitWriteLock();
应该小心使用,如果排它锁的部分需要很长的时间,由于它的使用。
类似的锁定结构
可升级的锁是惊人的相似到SQL Server的(共享意图去独占)。
- 要改写上述这些条款的声明,可升级的锁说,一个作家有意写的资源,但要分享与其他读者,而它[双]检查的条件,看它是否应该完全锁定并执行写。
如果没有意向锁的存在,你必须执行我应该让这种变化检查独占锁,它可以伤害并发里面。
你为什么不能分享升级?
如果在升级的锁是共享与其他可升级的锁,将有可能与其他升级锁业主的竞争条件。因此,你需要一次写入锁内的又一检查,不妨碍在首位其它读取取出做检查的好处。
示例
如果我们把所有的锁等待/进入/退出事件顺序,并锁定为平行内的工作,那么我们就可以写大理石的形式方案(电子
进入; 是W
等; X
退出; CR
检查资源; 先生
变异的资源; 研究
共享/读取; U
意图/升级; 是W
1 - 欧盟 - 铬 - WW ---- EW - 先生 - xWxU --------------
2 ---- --eR ---- xR的----------------尔 - XR ------
3 -------- eR-- --xR --------------------------
4 ----吴----------- -----------欧盟 - 铬 - 许----
在的话:T1进入升级/意向锁。 T4等待升级/意向锁。 T2和T3进入读锁。 T1同时检查资源,赢得了比赛,并在等待排/写锁。 T2&安培; T3退出他们的锁。 T1进入独占/写锁,使变化。 T4进入升级/意图锁,并不需要使它的变化和出口,而不会阻塞T2它确实在此期间另一个读。
在8要点。 ..
可升级的锁是:
- 任何作家使用;
- 谁就有可能先检查,然后决定不执行写入任何原因(失去竞争条件,或在Getsert模式);
- ,直到它知道它必须执行写谁不应该阻止读者。
- 于是就采取了排它锁,这样做
- 读者和谁
writelock - 检查 - NOWRITE出境
作家之间的竞争率接近零(写条件检查超快) - 即一个可升级的构造不会帮助读者吞吐量; -
其具有在写锁一次写入一个作家的概率是1〜因为无论:
-
ReadLock - 检查 - WriteLock-DoubleCheck
是如此之快,只将导致竞争的失败者每一次写操作万亿; - 所有的变化是唯一的(所有的改变必须发生,比赛就不能存在);的或的
- 最后获胜的变化(所有的更改还是一定会发生,即使他们不是唯一的)
-
升级不如果下列之一申请(包括但不限于)所需>
它也并不需要,如果锁(...){...}
是比较合适的,即:
的其中业绩是由你来定义的
的如果您查看锁定对象作为表和保护资源在层次较低的资源,这个比喻大约持有的
的在读锁最初的检查将是可选的,在可升级锁在支票是强制性的,因此它可以在一个单或双检查图案被使用。的
I'm very familiar with ReaderWriterLockSlim
but tried my hand at implementing EnterUpgradeableReadLock()
recently in a class... Soon after I realized that this is almost certainly a guaranteed deadlock when 2 or more threads run the code:
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
What am I missing here?
EDIT
Added code example of my scenario. The Run()
method would be called by 2 or more threads concurrently.
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();
}
}
}
A long time after the OP, but I don't agree with the currently accepted answer.
The statement Thread B --> enter upgradeable read lock
is incorrect. From the docs
And in response to your comments: it is intended for a very different usage to a Read-Write pattern.
TL;DR. Upgradeable mode is useful:
- if a writer must check a shared resource before writing to it, and (optionally) needs to avoid race conditions with other writers;
- and it should not stop readers until it is 100 % sure it must write to the shared resource;
- and it is quite likely a writer will decide it should not write to the shared resource once it has performed the check.
Or, in pseudocode, where this:
// no other writers or upgradeables allowed in here => no race conditions
EnterUpgradeableLock();
if (isWriteRequired()) { EnterWriteLock(); DoWrite(); ExitWriteLock(); }
ExitUpgradeableLock();
gives "better performance" than this:
EnterWriteLock(); if (isWriteRequired()) { DoWrite(); } ExitWriteLock();
It should be used with care if the exclusive lock sections take a very long time due to it's use of SpinLock.
A similar lock construct
The Upgradeable lock is surprisingly similar to a SQL server SIX lock (Shared with Intent to go eXclusive) .
- To rewrite the statement above in these terms, an Upgradeable lock says "a writer Intends to write to a resource, but wants to Share it with other readers while it [double]checks a condition to see if it should eXclusively lock and perform the write" .
Without the existence of an Intent lock, you must perform the "should I make this change" check inside an eXclusive lock, which can hurt concurrency.
Why can't you share Upgradeable?
If the Upgradeable lock was shareable with other Upgradeable locks it would be possible to have a race condition with other Upgradeable lock owners. You would therefore require yet another check once inside the Write lock, removing the benefits of doing the check without preventing other reads in the first place.
Example
If we view all lock wait/entry/exit events as sequential, and the work inside a lock as parallel, then we can write a scenario in "Marble" form (e
enter; w
wait; x
exit; cr
check resource; mr
mutate resource; R
Shared/Read; U
Intent/Upgradeable; W
eXclusive/Write):
1--eU--cr--wW----eW--mr--xWxU--------------
2------eR----xR----------------eR--xR------
3--------eR----xR--------------------------
4----wU----------------------eU--cr--xU----
In words: T1 enters the Upgradeable/Intent lock. T4 waits for the Upgradeable/Intent lock. T2 and T3 enter read locks. T1 meanwhile checks the resource, wins the race and waits for an eXclusive/Write lock. T2&T3 exit their locks. T1 enters the eXclusive/Write lock and makes the change. T4 enters the Upgradeable/Intent lock, doesn't need to make it's change and exits, without blocking T2 which does another read in the meantime.
In 8 bullet points...
The Upgradeable lock is:
- used by any Writer;
- who is likely to check first and then decide not to perform the Write for any reason (losing a race condition, or in a Getsert pattern);
- and who should not block Readers until it knows it must perform the Write;
- whereupon it will take out an exclusive lock and do so.
Upgradeable is not required if one of the following apply (including but not limited to):
- Contention rates between readers and writers who
writelock-check-nowrite-exit
are approximately zero (the write-condition check is super fast) - i.e. an Upgradeable construct doesn't help Reader throughput; The probability of having a writer which Writes once in a Write lock is ~1 because either:
ReadLock-Check-WriteLock-DoubleCheck
is so fast it only causes race losers once per trillion writes;- all changes are unique (all changes must happen, races can't exist); or
- the "last change wins" (all changes still must happen, even though they aren't unique)
It is also not required if a lock(...){...}
is more appropriate, i.e.:
- the probability of overlapping read and/or write windows is low (locks can be as much about preventing very rare events as protecting highly likely events, not to speak of simple Memory Barrier requirements)
- All your lock acquisitions are Upgradeable or Write, never Read ('duh')
Where "performance" is up to you to define
If you view the lock object as the table, and the protected resources as the resources lower in the hierarchy, this analogy approximately holds
The initial check in a Read lock would be optional, the check within the Upgradeable lock is mandatory, therefore it can be used in a single or double check pattern.
这篇关于ReaderWriterLockSlim.EnterUpgradeableReadLock()总是死机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!