11

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)を実行しようとするとブロックされます。

WaitOneでスレッド1がブロックされたら、ロックを解除する必要があると思いました。私は間違っていると思います。どうすれば修正できるのかわかりません。ところで-ロックブロックのコードは両方のスレッドで同期する必要があるため、ロックを追加しました。

長い投稿をありがとうございました。

4

2 に答える 2

13

  1. デッドロックになった理由

最初の短い答え: Set の Reset を見逃しました。

私はあなたのコードをコピーしました(中括弧を好みのスタイルに変更しました)。コメントで問題を説明します:

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

その部分が明確になったら、解決策に進みましょう。


  1. デッドロックの解決

と交換.Reset()する.Set()ので、 のデフォルト状態も から に変更する必要がManualResetEventありtrueますfalse

したがって、デッドロックを解決するには、次のようにコードを編集します [テスト済み]:

private ManualResetEvent _event = new ManualResetEvent (false);

private void process()
{
  //...
    lock(_event)
    {
        _event.WaitOne(); //Thread A will be here waiting for _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
    {
        //...
    }
}

上記のコードは、同時に 1 つのスレッドのみがロックに入ることを強制するだけでなく、入るprocessスレッドが を呼び出すスレッドが存在するまで待機することも強制しますStop


  1. しかし、競合状態が発生しました...修正します。

上記のコードは競合状態の病気に苦しんでいるため、仕事は終わっていません。複数のスレッドが を呼び出す場合に何が起こるかを想像する理由を理解するprocess。1 つのスレッドのみがロックに入り、 が呼び出されて _event が設定されるまで待機Stopします。その後、続行できます。_event.Set()ここで、Stops を呼び出したスレッドが を呼び出した直後に横取りされた場合に何が起こるかを考えてみましょ_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は 1 つだけStopです。processメソッドを呼び出しているスレッドがメソッドを呼び出すまで待機するように、追加の作業が行われましたStop


  1. そして今、あなたは再突入の問題を抱えています...それを修正します.

上記の解決策の方が優れています...そしてそれは完璧という意味ではありません。どうしたの?Stop を再帰的に呼び出した場合、または 2 つの異なるスレッドから同時に呼び出した場合、最初の呼び出しの実行中に 2 番目の呼び出しがプロセスのスレッドを進める可能性があるため、正しく機能しません...それが欲しい。メソッドを呼び出す複数のスレッドによる問題を防ぐには、読み取り/書き込みロックで十分Stopに見えましたが、そうではありませんでした。

これを解決するには、Stop が一度に 1 回だけ実行されるようにする必要があります。ロックでそれを行うことができます:

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

なぜ読み書きロックが必要なのですか? -あなたは尋ねるかもしれません-ロックを使用して、1つのスレッドのみがメソッドに入るのを確実にする場合Stop...?

読み取り/書き込みロックにより、メソッドのスレッドは、メソッドStopを呼び出している新しいスレッドを停止processできると同時に、既に存在していたスレッドを実行して終了するまで待機できるようになるためです。

なぜ必要なのManualResetEventですか?- あなたは尋ねるかもしれません - メソッド内のスレッドの実行を制御するための読み取り/書き込みロックが既にある場合はprocess...?

読み取り/書き込みロックは、メソッドが呼び出されるprocess前にメソッド内のコードの実行を防ぐことができないためです。Stop

それで、あなたは私たちがそれをすべて必要としています... それとも私たちですか?

まあ、それはあなたが持っている行動に依存するので、あなたが持っていたものではない問題を解決した場合に備えて、以下にいくつかの代替ソリューションを提供します.


  1. 代替動作による代替ソリューション

ロックは非常に理解しやすいですが、私の好みには少し多すぎます...特に、 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(ref _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
        }
    }
}

まだ正しい動作になっていませんか?わかりました、別のものを見てみましょう。


  1. 代替動作による代替ソリューション...再び

今回は、メソッドが呼び出される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(ref _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
        }
    }
}

あなたが望むものではありませんか?

わかりました、あきらめます...基本に戻りましょう。


  1. そして、あなたがすでに知っていたこと

...完全を期すために、両方のメソッドのアクセスが同期されていることを確認するだけでよく、プロセスのメソッドをいつでも実行できるようにする必要がある場合は、ロックだけで実行できます...そして、あなたはすでにそれを知っていました。

private object _syncroot = new object();

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

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

  1. 結論

そもそもデッドロックが発生した理由とその修正方法を見てきましたが、デッドロックがないからといってスレッドの安全性が保証されないことも発見しました。最後に、4 つの異なる動作と複雑さを持つ 3 つのソリューション (上記のポイント 4、5、6、および 7) を見てきました。全体として、マルチスレッドを使用した開発は非常に複雑な作業になる可能性があり、目標を明確に保ち、​​あらゆる場面で何がうまくいかないかを認識する必要があると結論付けることができます。少し妄想的になっても問題ないと言えますが、それはマルチスレッドだけに当てはまるわけではありません。

于 2012-06-26T00:29:20.803 に答える
3

Monitor.Wait(object) と ManualResetEvent.WaitOne() と混同されたと思います。

Monitor.Wait(object) はロックを解除し、ロックを取得するまで待ちます。ManualResetEvent.WaitOne() は、イベント ハンドルが通知されるまで現在のスレッドをブロックします。

また、ManualResetEvent オブジェクトを同時にロックとして使用しないことをお勧めします。コンパイラはエラーを生成しませんが、現在のように混乱を招く可能性があります。

于 2011-03-16T08:58:56.143 に答える