コードのデッドロックを防ぐための一般的な解決策は、どのスレッドがリソースにアクセスしているかに関係なく、ロックのシーケンスが共通の方法で発生することを確認することです。
たとえば、スレッドT1とT2が与えられ、T1がリソースAにアクセスし、次にBとT2がリソースBにアクセスし、次にAにアクセスします。必要な順序でリソースをロックすると、デッドロックが発生します。簡単な解決策は、特定のスレッドがリソースを使用する順序に関係なく、AをロックしてからBをロックすることです。
問題のある状況:
Thread1 Thread2
------- -------
Lock Resource A Lock Resource B
Do Resource A thing... Do Resource B thing...
Lock Resource B Lock Resource A
Do Resource B thing... Do Resource A thing...
考えられる解決策:
Thread1 Thread2
------- -------
Lock Resource A Lock Resource A
Lock Resource B Lock Resource B
Do Resource A thing... Do Resource B thing...
Do Resource B thing... Do Resource A thing...
私の質問は、デッドロック防止を保証するためにコーディングで使用されている他の手法、パターン、または一般的な方法は何ですか?
あなたが説明するテクニックは一般的であるだけではありません:それは常に機能することが証明されている1つのテクニックです。ただし、C++でスレッドコードをコーディングするときに従う必要のあるルールは他にもいくつかありますが、その中で最も重要なものは次のとおりです。
しばらく続けることもできますが、私の経験では、スレッドを操作する最も簡単な方法は、可能性のあるすべての人によく知られているパターンを使用することです。プロデューサー/コンシューマーパターンなどのコードを操作します。説明は簡単で、スレッドが相互に通信できるようにするために必要なツール(キュー)は1つだけです。結局のところ、2つのスレッドが互いに同期されるonlyの理由は、それらが通信できるようにするためです。
より一般的なアドバイス:
#include <thread>
#include <cassert>
#include <chrono>
#include <iostream>
#include <mutex>
void
nothing_could_possibly_go_wrong()
{
int flag = 0;
std::condition_variable cond;
std::mutex mutex;
int done = 0;
typedef std::unique_lock<std::mutex> lock;
auto const f = [&]
{
if(flag == 0) ++flag;
lock l(mutex);
++done;
cond.notify_one();
};
std::thread threads[2] = {
std::thread(f),
std::thread(f)
};
threads[0].join();
threads[1].join();
lock l(mutex);
cond.wait(l, [done] { return done == 2; });
// surely this can't fail!
assert( flag == 1 );
}
int
main()
{
for(;;) nothing_could_possibly_go_wrong();
}
デッドロックの回避に関しては、ロックの一貫した順序が最初で最後のWordです。
ロックレスプログラミング(スレッドがロックを待機しないため、サイクルの可能性がない)などの関連する手法がありますが、これは実際には「一貫性のないロック順序を回避する」ルールの特殊なケースです。すべてのロックを回避することにより、一貫性のないロックを回避します。残念ながら、ロックレスプログラミングには独自の問題があるため、万能薬でもありません。
範囲を少し広げたい場合は、デッドロックが発生したときにそれを検出する方法(何らかの理由でデッドロックを回避するようにプログラムを設計できない場合)と、デッドロックが発生したときにデッドロックを解除する方法があります(たとえば、常にタイムアウトでロックするか、デッドロックされたスレッドの1つにLock()コマンドを強制的に失敗させるか、デッドロックされたスレッドの1つを強制終了するだけです。しかし、そもそもデッドロックが発生しないようにすることよりも、それらはすべてかなり劣っていると思います。
(ところで、プログラムに潜在的なデッドロックがあるかどうかを自動で確認する方法が必要な場合は、valgrindのhelgrindツールを確認してください。コードのロックパターンを監視し、不整合があれば通知します-非常に便利です)
もう1つの手法は、トランザクションプログラミングです。ただし、これは通常、特殊なハードウェアを使用するため、あまり一般的ではありません(現在、そのほとんどは研究機関でのみ使用されています)。
各リソースは、異なるスレッドからの変更を追跡します。 (使用している)すべてのリソースに変更をコミットする最初のスレッドは、(それらのリソースを使用している)他のすべてのスレッドに勝ち、ロールバックされて、新しいコミットされた状態のリソースで再試行します。
主題を読むための単純な出発点は トランザクションメモリ です。
あなたはデザインレベルについて質問していますが、私はいくつかのより低いレベルのプログラミングプラクティスを追加します。
あなたが言及した既知のシーケンスの解決策に代わるものではありませんが、Andrei Alexandrescuは、ロックの取得が意図されたメカニズムを通じて行われるコンパイル時チェックのいくつかの手法について書いています。 http://www.informit.com/articles/article.aspx?p=25298 を参照してください