- デッドロックになった理由
最初の短い答え: 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
{
//...
}
}
その部分が明確になったら、解決策に進みましょう。
- デッドロックの解決
と交換.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
。
- しかし、競合状態が発生しました...修正します。
上記のコードは競合状態の病気に苦しんでいるため、仕事は終わっていません。複数のスレッドが を呼び出す場合に何が起こるかを想像する理由を理解する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
。
- そして今、あなたは再突入の問題を抱えています...それを修正します.
上記の解決策の方が優れています...そしてそれは完璧という意味ではありません。どうしたの?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
それで、あなたは私たちがそれをすべて必要としています... それとも私たちですか?
まあ、それはあなたが持っている行動に依存するので、あなたが持っていたものではない問題を解決した場合に備えて、以下にいくつかの代替ソリューションを提供します.
- 代替動作による代替ソリューション
ロックは非常に理解しやすいですが、私の好みには少し多すぎます...特に、 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
}
}
}
まだ正しい動作になっていませんか?わかりました、別のものを見てみましょう。
- 代替動作による代替ソリューション...再び
今回は、メソッドが呼び出される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
}
}
}
あなたが望むものではありませんか?
わかりました、あきらめます...基本に戻りましょう。
- そして、あなたがすでに知っていたこと
...完全を期すために、両方のメソッドのアクセスが同期されていることを確認するだけでよく、プロセスのメソッドをいつでも実行できるようにする必要がある場合は、ロックだけで実行できます...そして、あなたはすでにそれを知っていました。
private object _syncroot = new object();
private void process()
{
//...
lock(_syncroot)
{
//...
}
}
internal void Stop()
{
lock(_syncroot)
{
//...
}
}
- 結論
そもそもデッドロックが発生した理由とその修正方法を見てきましたが、デッドロックがないからといってスレッドの安全性が保証されないことも発見しました。最後に、4 つの異なる動作と複雑さを持つ 3 つのソリューション (上記のポイント 4、5、6、および 7) を見てきました。全体として、マルチスレッドを使用した開発は非常に複雑な作業になる可能性があり、目標を明確に保ち、あらゆる場面で何がうまくいかないかを認識する必要があると結論付けることができます。少し妄想的になっても問題ないと言えますが、それはマルチスレッドだけに当てはまるわけではありません。