我遇到了在锁定manualResetEvent实例时导致的死锁。我无法弄清楚如何解决它。我将不胜感激。
我在一个由不同线程执行的类中有两个方法:
private ManualResetEvent _event = new ManualResetEvent (true);
private void process(){
...
lock(_event){
_event.WaitOne();
...
}
}
internal void Stop(){
_event.Reset();
lock(_event){
...
}
}
第一个线程使用了锁并在_event.WaitOne()中被阻止;
socond线程执行了_event.Reset()行;并在尝试执行锁定(_event)时被阻止。
我认为在WaitOne上阻塞线程1时,应该释放锁。我想我错了。我不知道如何解决它。 b.t.w - 我添加了锁,因为锁块中的代码应该在两个线程中同步。
再次感谢并为这篇长篇文章感到抱歉。
答案 0 :(得分:9)
简短回答:您错过了重置设置。
我已经复制了你的代码(将大括号更改为我喜欢的格式),我将在评论中解释这个问题:
private ManualResetEvent _event = new ManualResetEvent (true);
private void process()
{
//...
lock(_event)
{
_event.WaitOne(); //Thread A is here waiting _event to be set
//...
}
}
internal void Stop()
{
_event.Reset(); //But thread B just did reset _event
lock(_event) //And know thread B is here waiting... nobody is going to set _event
{
//...
}
}
随着这一部分的明确,让我们继续前进到解决方案。
由于我们要与.Reset()
交换.Set()
,我们还必须将ManualResetEvent
的默认状态从true
更改为false
。< / p>
因此,要解决死锁,请按以下方式编辑代码[测试]:
private ManualResetEvent _event = new ManualResetEvent (false);
private void process()
{
//...
lock(_event)
{
_event.WaitOne(); //Thread A will be here waiting _event to be set
//...
}
}
internal void Stop()
{
_event.Set(); //And thread B will set it, so thread a can continue
lock(_event) //And when thread a releases the lock on _event thread b can enter
{
//...
}
}
上面的代码不仅强制执行只有一个线程可以同时进入锁,而且强制进入process
的线程将等到有一个调用Stop
的线程。
这项工作没有完成,因为上面的代码患有种族疾病。要理解为什么想象在多个线程调用process
的情况下会发生什么。只有一个线程将进入锁定并等待调用Stop
并设置_event,之后它可以继续。现在,考虑如果调用Stops的线程在调用_event.Set()
之后被抢占会发生什么,等待_event.WaitOne()
的线程继续并离开锁...现在你无法告诉如果等待进入process
锁定的另一个线程将进入,或者Stop
中被抢占的线程将继续并在该方法中进入锁定。这是一种竞争条件,我不认为你想要那个特定的竞争条件。
那就是说我为你提供了一个更好的解决方案[测试]:
private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
private void process()
{
//...
_readWrite.EnterReadLock();
_event.WaitOne();
try
{
//...
}
finally
{
_readWrite.ExitReadLock();
}
}
internal void Stop()
{
//there are three relevant thread positions at the process method:
//a) before _readWrite.EnterReadLock();
//b) before _event.WaitOne();
//c) after _readWrite.EnterReadLock();
_event.Set(); //Threads at position b start to advance
Thread.Sleep(1); //We want this thread to preempt now!
_event.Reset(); //And here we stop them
//Threads at positions a and b wait where they are
//We wait for any threads at position c
_readWrite.EnterWriteLock();
try
{
//...
}
finally
{
_readWrite.ExitWriteLock();
//Now the threads in position a continues...
// but are halted at position b
//Any thread in position b will wait until Stop is called again
}
}
阅读代码中的注释以了解其工作原理。简单来说,它需要一个读写锁的允许来允许多个线程进入方法process
但只有一个进入Stop
。虽然已完成其他工作以确保调用方法process
的线程将等到线程调用方法Stop
。
上面的解决方案更好......而且这并不意味着完美。它有什么问题?好吧,如果你以递归方式调用Stop,或者如果你同时从两个不同的线程调用它,它将无法正常工作,因为第二次调用可能会在第一次调用执行时使进程中的线程...而且我认为你不要想要那个。它的确具有读写锁定足以防止调用方法Stop
的多个线程出现任何问题,但事实并非如此。
要解决这个问题,我们需要确保Stop当时只执行一次。你可以用锁来做到这一点:
private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
//I'm going to use _syncroot, you can use any object...
// as long as you don't lock on it somewhere else
private object _syncroot = new object();
private void process()
{
//...
_readWrite.EnterReadLock();
_event.WaitOne();
try
{
//...
}
finally
{
_readWrite.ExitReadLock();
}
}
internal void Stop()
{
lock(_syncroot)
{
//there are three relevant thread positions at the process method:
//a) before _readWrite.EnterReadLock();
//b) before _event.WaitOne();
//c) after _readWrite.EnterReadLock();
_event.Set(); //Threads at position b start to advance
Thread.Sleep(1); //We want this thread to preempt now!
_event.Reset(); //And here we stop them
//Threads at positions a and b wait where they are
//We wait for any threads at position c
_readWrite.EnterWriteLock();
try
{
//...
}
finally
{
_readWrite.ExitWriteLock();
//Now the threads in position a continues...
// but are halted at position b
//Any thread in position b will wait until Stop is called again
}
}
}
为什么我们需要读写锁? - 你可能会问 - 如果我们使用锁来确保只有一个线程进入方法Stop
......?
因为读写锁也允许方法Stop
的线程停止调用方法process
的新线程,同时允许那些已经存在的线程执行并等待它们完成
为什么我们需要ManualResetEvent
? - 您可能会问 - 如果我们已经使用读写锁来控制方法process
中线程的执行...?
因为在调用方法process
之前,读写锁无法阻止方法Stop
中的代码执行。
那么,你们我们需要所有这些......还是我们呢?
那么,这取决于你有什么行为,所以如果我确实解决了你不具备的问题,我会在下面提供一些替代解决方案。
Lock非常容易理解,但对我来说有点太多了......特别是如果没有必要确保每次对Stop的并发调用都有机会允许执行线程方法process
。
如果是这种情况,那么您可以按如下方式重写代码:
private ManualResetEvent _event = new ManualResetEvent (false);
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
private int _stopGuard;
private void process()
{
//...
_readWrite.EnterReadLock();
_event.WaitOne();
try
{
//...
}
finally
{
_readWrite.ExitReadLock();
}
}
internal void Stop()
{
if(Interlocked.CompareExchange(_stopGuard, 1, 0) == 0)
{
//there are three relevant thread positions at the process method:
//a) before _readWrite.EnterReadLock();
//b) before _event.WaitOne();
//c) after _readWrite.EnterReadLock();
_event.Set(); //Threads at position b start to advance
Thread.Sleep(1); //We want this thread to preempt now!
_event.Reset(); //And here we stop them
//Threads at positions a and b wait where they are
//We wait for any threads at position c
_readWrite.EnterWriteLock();
try
{
//...
}
finally
{
_readWrite.ExitWriteLock();
//Now the threads in position a continues...
// but are halted at position b
//Any thread in position b will wait until Stop is called again
}
}
}
还没有正确的行为?好的,让我们看看另一个。
这次我们将看到如何在调用方法process
之前允许多个线程进入方法Stop
。
private ReaderWriterLockSlim _readWrite = new ReaderWriterLockSlim();
private int _stopGuard;
private void process()
{
//...
_readWrite.EnterReadLock();
try
{
//...
}
finally
{
_readWrite.ExitReadLock();
}
}
internal void Stop()
{
if(Interlocked.CompareExchange(_stopGuard, 1, 0) == 0)
{
//there are two relevant thread positions at the process method:
//a) before _readWrite.EnterReadLock();
//b) after _readWrite.EnterReadLock();
//We wait for any threads at position b
_readWrite.EnterWriteLock();
try
{
//...
}
finally
{
_readWrite.ExitWriteLock();
//Now the threads in position a continues...
// and they will continue until halted when Stop is called again
}
}
}
不是你想要的?
好的,我放弃了......让我们回到基础。
...为了完整起见,如果您只需要确保两个方法的访问权限是同步的,并且您可以允许进程中的方法随时运行,那么您只需使用锁即可。 ..你已经知道了。
private object _syncroot = new object();
private void process()
{
//...
lock(_syncroot)
{
//...
}
}
internal void Stop()
{
lock(_syncroot)
{
//...
}
}
我们已经看到了为什么首先发生了死锁以及如何解决它,但我们还发现没有死锁并不是线程安全的保证。最后,我们看到了三种解决方案(上面的第4,5,6和7点),具有四种不同的行为和复杂性。总而言之,我们可以得出结论,使用多线程进行开发可能是一项非常复杂的任务,我们需要保持目标清晰,并了解每一个问题都可能出现问题。你可以说有点偏执,这并不适用于多线程。
答案 1 :(得分:3)
我猜你与Monitor.Wait(对象)和ManualResetEvent.WaitOne()混淆了。
Monitor.Wait(object)释放锁并等待它获取锁定。 ManualResetEvent.WaitOne()阻止当前线程,直到事件句柄发出信号。
我还建议不要同时使用ManualResetEvent对象作为锁。即使编译器不会生成错误,这可能会像您现在一样产生混淆。