我在锁定manualResetEvent实例时遇到死锁。我不知道如何解决。我将不胜感激。

我在一个由不同线程执行的类中有2个方法:

private ManualResetEvent _event = new ManualResetEvent (true);

private void process(){
  ...
  lock(_event){
    _event.WaitOne();
    ...
  }
}

internal void Stop(){
  _event.Reset();
  lock(_event){
    ...
  }
}


第一个线程锁定该锁,并在_event.WaitOne();中被阻塞;

第二个线程执行了_event.Reset();行。并且在尝试执行lock(_event)时被阻止。

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

再次感谢,对于冗长的帖子,深表歉意。

最佳答案

1.为什么陷入僵局

首先简短的回答:您错过了Reset for Set。

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

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

因此,要解决死锁,请按如下所示测试代码:

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


阅读代码中的注释以了解其工作原理。简而言之,它需要使用Read-Write锁来允许多个线程输入方法process,但只有一个线程输入Stop。尽管还做了其他工作来确保正在调用方法process的线程将等待,直到线程调用方法Stop



4.现在您遇到了重入问题...正在解决。

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

因为Read-Write锁还允许方法Stop处的线程停止正在调用方法process的较新线程,同时允许已经存在的线程执行并等待它们完成。

为什么我们需要ManualResetEvent? -您可能会问-如果我们已经具有读写锁来控制方法process中的线程执行?

因为读写锁不能阻止在调用方法process之前执行方法Stop中的代码。

所以,您需要我们所有的……还是我们?

好吧,这取决于您的行为,因此,如果我确实解决了您所没有的问题,我将在下面提供一些替代解决方案。



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

锁很容易理解,但是对我来说有点太高了……特别是如果不需要确保每次并发调用Stop都有机会允许在方法。

如果是这种情况,则可以按如下所示重写代码:

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之前,如何允许多个线程进入方法process

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/

10-13 06:22