.net 当写入程序试图进入写锁时,如何避免阻塞ReaderWriterLockSlim读卡器
我正在使用.net 当写入程序试图进入写锁时,如何避免阻塞ReaderWriterLockSlim读卡器,.net,multithreading,readerwriterlockslim,.net,Multithreading,Readerwriterlockslim,我正在使用ReaderWriterLockSlim来保护一些操作。我更喜欢读写器,这样当读写器长时间持有锁,而写写器试图获取写锁时,以后的读写器不会被写器的尝试所阻止(如果写器在lock.EnterWriteLock()上被阻止,则会发生这种情况) 为此,我认为编写器可以使用TryEnterWriteLock,在循环中有一个短的超时,这样后续的读卡器仍然能够获得读锁,而编写器不能。然而,令我惊讶的是,我发现调用TryEnterWriteLock失败会改变锁的状态,从而阻止未来的读卡器。概念验证代
ReaderWriterLockSlim
来保护一些操作。我更喜欢读写器,这样当读写器长时间持有锁,而写写器试图获取写锁时,以后的读写器不会被写器的尝试所阻止(如果写器在lock.EnterWriteLock()
上被阻止,则会发生这种情况)
为此,我认为编写器可以使用TryEnterWriteLock
,在循环中有一个短的超时,这样后续的读卡器仍然能够获得读锁,而编写器不能。然而,令我惊讶的是,我发现调用TryEnterWriteLock
失败会改变锁的状态,从而阻止未来的读卡器。概念验证代码:
System.Threading.ReaderWriterLockSlim myLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
System.Threading.Thread t1 = new System.Threading.Thread(() =>
{
Console.WriteLine("T1:{0}: entering read lock...", DateTime.Now);
myLock.EnterReadLock();
Console.WriteLine("T1:{0}: ...entered read lock.", DateTime.Now);
System.Threading.Thread.Sleep(10000);
});
System.Threading.Thread t2 = new System.Threading.Thread(() =>
{
System.Threading.Thread.Sleep(1000);
while (true)
{
Console.WriteLine("T2:{0}: try-entering write lock...", DateTime.Now);
bool result = myLock.TryEnterWriteLock(TimeSpan.FromMilliseconds(1500));
Console.WriteLine("T2:{0}: ...try-entered write lock, result={1}.", DateTime.Now, result);
if (result)
{
// Got it!
break;
}
System.Threading.Thread.Yield();
}
System.Threading.Thread.Sleep(9000);
});
System.Threading.Thread t3 = new System.Threading.Thread(() =>
{
System.Threading.Thread.Sleep(2000);
Console.WriteLine("T3:{0}: entering read lock...", DateTime.Now);
myLock.EnterReadLock();
Console.WriteLine("T3:{0}: ...entered read lock!!!!!!!!!!!!!!!!!!!", DateTime.Now);
System.Threading.Thread.Sleep(8000);
});
此代码的输出为:
T1:18-09-2015 16:29:49: entering read lock...
T1:18-09-2015 16:29:49: ...entered read lock.
T2:18-09-2015 16:29:50: try-entering write lock...
T3:18-09-2015 16:29:51: entering read lock...
T2:18-09-2015 16:29:51: ...try-entered write lock, result=False.
T2:18-09-2015 16:29:51: try-entering write lock...
T2:18-09-2015 16:29:53: ...try-entered write lock, result=False.
T2:18-09-2015 16:29:53: try-entering write lock...
T2:18-09-2015 16:29:54: ...try-entered write lock, result=False.
T2:18-09-2015 16:29:54: try-entering write lock...
T2:18-09-2015 16:29:56: ...try-entered write lock, result=False.
T2:18-09-2015 16:29:56: try-entering write lock...
T2:18-09-2015 16:29:57: ...try-entered write lock, result=False.
T2:18-09-2015 16:29:57: try-entering write lock...
T2:18-09-2015 16:29:59: ...try-entered write lock, result=False.
T2:18-09-2015 16:29:59: try-entering write lock...
正如您所看到的,即使线程2(“Writer”)没有获得Writer锁,也没有在EnterWriteLock
调用中,线程3也会被永久阻止。我可以在ReaderWriterLock
中看到类似的行为
我做错什么了吗?如果没有,当一个作者排队时,我有什么选择来帮助读者呢?我无能为力,但我相信这是一个.NET Framework错误(更新:我有)。以下简单程序(上述程序的简化版本)说明:
var myLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
var t1 = new Thread(() =>
{
Console.WriteLine("T1:{0}: entering read lock...", DateTime.Now);
myLock.EnterReadLock();
Console.WriteLine("T1:{0}: ...entered read lock.", DateTime.Now);
Thread.Sleep(50000);
Console.WriteLine("T1:{0}: exiting", DateTime.Now);
myLock.ExitReadLock();
});
var t2 = new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine("T2:{0}: try-entering write lock...", DateTime.Now);
bool result = myLock.TryEnterWriteLock(3000);
Console.WriteLine("T2:{0}: ...try-entered write lock, result={1}.", DateTime.Now, result);
Thread.Sleep(50000);
if (result)
{
myLock.ExitWriteLock();
}
Console.WriteLine("T2:{0}: exiting", DateTime.Now);
});
var t3 = new Thread(() =>
{
Thread.Sleep(2000);
Console.WriteLine("T3:{0}: entering read lock...", DateTime.Now);
myLock.EnterReadLock();
Console.WriteLine("T3:{0}: ...entered read lock!!!!!!!!!!!!!!!!!!!", DateTime.Now);
Thread.Sleep(50000);
myLock.ExitReadLock();
Console.WriteLine("T3:{0}: exiting", DateTime.Now);
});
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
以下事件发生的顺序很简单,没有锁定车队,没有比赛,没有环路或其他任何事情
T1
获取读锁T2
尝试获取写锁并阻塞,等待超时(因为T1
持有锁)T3
尝试获取读锁并阻塞(因为T2
在等待写锁时被阻塞,根据文档,这意味着所有其他读卡器都被阻塞,直到超时)T2
的超时过期。根据文档,T3
现在应该唤醒并获取读取锁。但是,这种情况不会发生,T3
被永久阻止(或者,在本例中,在T1
离开读取锁定之前的50秒内)在AICT中,
exitmolock
应该是exit并唤醒适当的服务员
我同意Mormegil
的回答,即您观察到的行为似乎不符合以下州的文件:
当线程被阻止等待进入写入模式时,尝试进入读取模式或可升级模式的其他线程将被阻止,直到所有等待进入写入模式的线程超时或进入写入模式,然后退出
请注意,有两种记录在案的方式,其他等待进入读取模式的线程将停止等待:
超时TryEnterWriteLock
成功,然后通过调用TryEnterWriteLock
释放锁ExitWriteLock
- 重置指示不再等待进入写入模式的标志。这是通过打电话来完成的
- 向等待的线程发出信号,指示它们应该重试获取所需的锁。此信令由对的调用执行
TryEnterWriteLock
超时时,只执行第一步(重置标志),而不执行第二步(向等待的线程发出重试获取锁的信号)。因此,在您的例子中,试图获取读锁的线程一直在无限期地等待,因为它从未被“告知”应该唤醒并再次检查标志
正如Mormegil
所指出的,将中的调用从exitmolock()
更改为exitandwakeuppropertedwaiters()
似乎就是解决问题所需的全部。因为修复看起来很简单,我也倾向于认为这只是一个被忽略的bug
这些信息如何有用?
理解原因使我们认识到“buggy”行为的影响在一定程度上是有限的。如果线程在某个线程调用TryEnterWriteLock
之后,而在TryEnterWriteLock
调用超时之前,试图获取锁,则它只会导致线程“无限期”阻塞。而且它不会无限期地阻塞。当其他线程正常释放其锁时,它最终会解除阻塞,这是本例中的预期情况
这也意味着,在TryEnterWriteLock
超时后,任何试图进入读取模式的线程都可以毫无问题地执行此操作
为了说明这一点,请运行以下代码段:
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static Stopwatch stopwatch = new Stopwatch();
static void Log(string logString)
{
Console.WriteLine($"{(long)stopwatch.Elapsed.TotalMilliseconds:D5}: {logString}");
}
static void Main(string[] args)
{
stopwatch.Start();
// T1 - Initial reader
new Thread(() =>
{
Log("T1 trying to enter READ mode...");
rwLock.EnterReadLock();
Log("T1 entered READ mode successfully!");
Thread.Sleep(10000);
rwLock.ExitReadLock();
Log("T1 exited READ mode.");
}).Start();
// T2 - Writer times out.
new Thread(() =>
{
Thread.Sleep(1000);
Log("T2 trying to enter WRITE mode...");
if (rwLock.TryEnterWriteLock(2000))
{
Log("T2 entered WRITE mode successfully!");
rwLock.ExitWriteLock();
Log("T2 exited WRITE mode.");
}
else
{
Log("T2 timed out! Unable to enter WRITE mode.");
}
}).Start();
// T3 - Reader blocks longer than it should, BUT...
// is eventually unblocked by T4's ExitReadLock().
new Thread(() =>
{
Thread.Sleep(2000);
Log("T3 trying to enter READ mode...");
rwLock.EnterReadLock();
Log("T3 entered READ mode after all! T4's ExitReadLock() unblocked me.");
rwLock.ExitReadLock();
Log("T3 exited READ mode.");
}).Start();
// T4 - Because the read attempt happens AFTER T2's timeout, it doesn't block.
// Also, once it exits READ mode, it unblocks T3!
new Thread(() =>
{
Thread.Sleep(4000);
Log("T4 trying to enter READ mode...");
rwLock.EnterReadLock();
Log("T4 entered READ mode successfully! Was not affected by T2's timeout \"bug\"");
rwLock.ExitReadLock();
Log("T4 exited READ mode. (Side effect: wakes up any other waiter threads)");
}).Start();
}
输出:
00000:T1正在尝试进入读取模式…00001:T1成功进入读取模式
01011:T2正在尝试进入写入模式…
02010:T3试图进入读取模式…
03010:T2超时!无法进入写入模式。
04013:T4试图进入读取模式…
04013:T4成功进入读取模式!未受T2超时“错误”的影响
04013:T4退出读取模式。(副作用:唤醒任何其他服务员线程)
04013:T3毕竟进入了读取模式!T4的ExitReadLock()解除了我的阻止。
04013:T3退出读取模式。
10005:T1已退出读取模式 还请注意,
T4
读卡器线程并不是解除阻塞所必需的