gpt4 book ai didi

.net - 锁定 manualResetEvent 时死锁

转载 作者:行者123 更新时间:2023-12-04 10:30:18 25 4
gpt4 key购买 nike

我在锁定 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();并且在尝试执行 lock(_event) 时被阻止。

我认为当线程1在WaitOne上被阻塞时,应该释放锁。我想我错了。我不知道我该如何修复它。
b.t.w - 我添加了锁,因为锁块中的代码应该在两个线程中同步。

再次感谢并为长篇文章感到抱歉。

最佳答案

1. 为什么会陷入僵局

首先是简短的回答:您错过了重置设置。

我已经复制了您的代码(将大括号更改为我喜欢的样式),我将在评论中解释问题:

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
{
//...
}
}

清楚这部分后,让我们继续解决方案。

2. 解决僵局

由于我们要将 .Reset().Set() 交换,因此我们还必须将 ManualResetEvent 的默认状态从 true 更改为 false

因此,要解决死锁,请按如下方式编辑代码 [tested]:
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

3. 但是你有一个竞争条件......修复它。

工作没有完成,因为上面的代码患有竞争条件的疾病。要理解为什么想象在多个线程调用 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

4. 现在你遇到了一个再入问题......修复它。

上面的解决方案更好......但这并不意味着完美。它出什么问题了?好吧,如果你递归调用 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 之前执行。

所以,我们需要所有这些……或者我们需要吗?

好吧,这取决于你的行为,所以如果我确实解决了一个你没有遇到的问题,我在下面提供了一些替代解决方案。

5. 具有替代行为的替代解决方案

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
}
}
}

还不是正确的行为?好吧,让我们看看另一个。

6.具有替代行为的替代解决方案......再次

这次我们将看看如何在方法 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
}
}
}

不是你想要的?

好吧,我放弃了……让我们回到基础。

7. 你已经知道的

...为了完整起见,如果您只需要确保两个方法的访问是同步的,并且您可以允许进程中的方法随时运行,那么您可以只用锁来完成...和你已经知道了。
private object _syncroot = new object();

private void process()
{
//...
lock(_syncroot)
{
//...
}
}

internal void Stop()
{
lock(_syncroot)
{
//...
}
}

7. 结论

我们已经看到了为什么会首先发生死锁以及如何修复它,但我们也发现没有死锁并不能保证线程安全。最后,我们看到了具有四种不同行为和复杂性的三种解决方案(上面的第 4、5、6 和 7 点)。总而言之,我们可以得出结论,使用多线程进行开发可能是一项非常复杂的任务,我们需要保持明确的目标,并注意每时每刻可能出现的问题。您可以说有点偏执是可以的,这不仅适用于多线程。

关于.net - 锁定 manualResetEvent 时死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5322739/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com