マルチスレッドアプリケーションを作成するときに発生する最も一般的な問題の1つは、デッドロックです。
コミュニティへの私の質問は次のとおりです。
デッドロックとは何ですか?
それらをどのように検出しますか?
それらを処理しますか?
そして最後に、どのようにしてそれらが発生するのを防ぎますか?
マルチスレッドアプリケーションを作成するときに発生する最も一般的な問題の1つは、デッドロックです。
コミュニティへの私の質問は次のとおりです。
デッドロックとは何ですか?
それらをどのように検出しますか?
それらを処理しますか?
そして最後に、どのようにしてそれらが発生するのを防ぎますか?
回答:
ロックは、複数のプロセスが同時に同じリソースにアクセスしようとしたときに発生します。
1つのプロセスは敗北し、もう1つのプロセスが完了するのを待つ必要があります。
デッドロックは、それが終了することができます前に、まず必要であることを待っているプロセスがまだ別のリソースへの保持している場合に発生します。
したがって、例:
リソースAとリソースBはプロセスXとプロセスYによって使用されます
デッドロックを回避する最良の方法は、プロセスがこのように交差するのを回避することです。できるだけロックする必要性を減らします。
データベースでは、1つのトランザクションでさまざまなテーブルに多くの変更を加えることを避け、トリガーを避けて、できるだけoptimistic / dirty / nolock読み取りに切り替えます。
犯罪映画からのデッドロック状況の実際の(実際ではない)例を説明しましょう。犯罪者が人質を保持していることを想像してみてください。反対に、警官も犯罪者の友人である人質を保持しています。この場合、警官が彼の友人を手放すことができなければ、犯罪者は人質を手放すつもりはありません。また、警官は犯罪者が人質を解放しない限り、犯罪者の友人を手放すつもりはありません。双方がお互いに最初のステップを主張しているため、これは無限の信頼できない状況です。
つまり、2つのスレッドが2つの異なるリソースを必要とし、それぞれに他のスレッドが必要とするリソースのロックがある場合、それはデッドロックです。
あなたは女の子と付き合っており、議論の1日後、双方はお互いに心を砕かれ、I-am-sorry-and-i-missed-youの電話を待っています。この状況では、一方が他方からI-am-sorryコールを受信した場合にのみ、両側が相互に通信する必要があります。どちらも通信を開始せず、パッシブ状態で待機しないため、どちらも相手が通信を開始するまで待機し、最終的にデッドロック状態になります。
デッドロックは、同時に取得できるロックが2つ以上あり、それらが異なる順序で取得されている場合にのみ発生します。
デッドロックを回避する方法は次のとおりです。
デッドロックを定義するには、最初にプロセスを定義します。
プロセス :私たちが知っているように、プロセスはprogram
実行中です。
リソース :プログラムプロセスを実行するには、いくつかのリソースが必要です。リソースカテゴリには、メモリ、プリンタ、CPU、オープンファイル、テープドライブ、CD-ROMなどが含まれます。
デッドロック :デッドロックは、2つ以上のプロセスがいくつかのリソースを保持し、さらにいくつかのリソースを取得しようとする状況または状態であり、実行が完了するまでリソースを解放できません。
デッドロック状態または状況
上の図では、2つのプロセスP1とp2があり、2つのリソースR1とR2があります。
リソースR1はプロセスP1に割り当てられ、リソースR2はプロセスp2に割り当てられます。プロセスP1の実行を完了するにはリソースR2が必要であるため、P1はR2を要求しますが、R2はすでにP2に割り当てられています。
同様に、プロセスP2の実行を完了するにはR1が必要ですが、R1はすでにP1に割り当てられています。
どちらのプロセスも、実行が完了するまで、および実行を完了するまでリソースを解放できません。したがって、どちらも別のリソースを待っており、永久に待機します。したがって、これはデッドロック状態です。
デッドロックが発生するためには、4つの条件が真でなければなりません。
上記の図では、これらすべての条件が満たされています。
デッドロックは、スレッドが発生しないことを待っているときに発生します。
通常、スレッドが前の所有者によって解放されなかったミューテックスまたはセマフォを待機しているときに発生します。
また、次のような2つのスレッドと2つのロックが関係する状況でも頻繁に発生します。
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
発生すると予想されることが実行されないか、アプリケーションが完全にハングするため、通常はそれらを検出します。
セクションDeadlockの下にあるこの素晴らしい記事をご覧ください。それはC#にありますが、アイデアは他のプラットフォームでも同じです。読みやすいようにここに引用します
デッドロックは、2つのスレッドがそれぞれ他のスレッドが保持するリソースを待機するときに発生するため、どちらも続行できません。これを説明する最も簡単な方法は、2つのロックを使用することです。
object locker1 = new object();
object locker2 = new object();
new Thread (() => {
lock (locker1)
{
Thread.Sleep (1000);
lock (locker2); // Deadlock
}
}).Start();
lock (locker2)
{
Thread.Sleep (1000);
lock (locker1); // Deadlock
}
デッドロックは、OSのマルチプロセッシング/マルチプログラミングの問題における一般的な問題です。2つのプロセスP1、P2と2つのグローバルに共有可能なリソースR1、R2があり、クリティカルセクションで両方のリソースにアクセスする必要があるとします。
最初に、OSはR1をプロセスP1に割り当て、R2をプロセスP2に割り当てます。両方のプロセスが同時に実行されているため、コードの実行を開始できますが、プロセスがクリティカルセクションに到達すると問題が発生します。したがって、プロセスR1はプロセスP2がR2を解放するのを待機し、その逆も同様です。したがって、プロセスR1は永久に待機します(DEADLOCK CONDITION)。
小さなアナロジー...
あなたの母親(OS)、
あなた(P1)、
あなたの兄弟(P2)、
アップル(R1)、
ナイフ(R2)、
クリティカルセクション(ナイフでリンゴを切る)。あなたのお母さんは初めにあなたに兄弟にリンゴとナイフを渡します。
両方とも幸せで遊んでいます(コードを実行しています)。
誰かが、ある時点でリンゴ(クリティカルセクション)をカットしたいと考えています。
あなたはあなたの兄弟にリンゴを与えたくありません。
あなたの兄弟はあなたにナイフを与えたくないのです。
ですので、あなた方二人は非常に長い間待つことになります:)
デッドロックは、2つのスレッドがロックを取得して、どちらかが進行しないようにするときに発生します。それらを回避する最善の方法は、慎重に開発することです。多くの組み込みシステムは、ウォッチドッグタイマー(一定の時間ハングした場合にシステムをリセットするタイマー)を使用して、システムを保護します。
デッドロック状況を理解するための古典的で非常にシンプルなプログラム:-
public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println(initialized);
}
}
メインスレッドはLazy.mainを呼び出すときに、クラスLazyが初期化されているかどうかを確認し、クラスの初期化を開始します。メインスレッドは、初期化をfalseに設定し、runメソッドが初期化をtrueに設定したバックグラウンドスレッドを作成して開始し、バックグラウンドスレッドが完了するまで待機します。
今回は、クラスは現在別のスレッドによって初期化されています。このような状況では、バックグラウンドスレッドである現在のスレッドは、初期化が完了するまでClassオブジェクトを待機します。残念ながら、初期化を行っているスレッド、メインスレッドは、バックグラウンドスレッドが完了するのを待っています。2つのスレッドが相互に待機しているため、プログラムはデッドロックされています。
デッドロックは、単一のプロセス/スレッドがアクションを実行することができないシステムの状態です。他の人が述べたように、デッドロックは通常、各プロセス/スレッドが別の(または同じ)プロセス/スレッドによってすでにロックされているリソースへのロックを取得したい状況の結果です。
それらを見つけて回避するには、さまざまな方法があります。1つは、非常に熱心に考えたり、多くのことを試みたりすることです。ただし、並列処理は非常に困難であり、ほとんど(すべてではないにしても)の人々は問題を完全に回避することはできません。
これらの種類の問題への対処を真剣に考えている場合は、より正式な方法がいくつか役立ちます。私が知っている最も実用的な方法は、プロセス理論的アプローチを使用することです。ここでは、システムをいくつかのプロセス言語(CCS、CSP、ACP、mCRL2、LOTOSなど)でモデル化し、利用可能なツールを使用してデッドロック(およびおそらく他のいくつかのプロパティ)を(モデル化)チェックします。使用するツールセットの例は、FDR、mCRL2、CADP、およびUppaalです。一部の勇敢な魂は、純粋にシンボリックな方法を使用して、システムがデッドロックなしであることを証明することさえできます(定理を証明する; Owicki-Griesを探す)。
ただし、これらの正式な方法では、通常、ある程度の努力が必要です(たとえば、プロセス理論の基礎を学ぶ)。しかし、それは単にこれらの問題が難しいという事実の結果だと思います。
上記のいくつかの説明はいいです。これも役立つことを願っています:https : //ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
データベースで、セッション(oraなど)が別のセッション(データなど)によって保持されるリソースを必要とするが、そのセッション(データ)も最初のセッション(ora)によって保持されるリソースを必要とする場合。2つ以上のセッションが含まれる場合もありますが、考え方は同じです。実際、デッドロックは一部のトランザクションが機能し続けるのを妨げます。次に例を示します。ORA-DATAがロックAを保持してロックBを要求し、SKUがロックBを保持してロックAを要求するとします。
おかげで、
共有リソースへのアクセスを制御するためにロックを使用すると、デッドロックが発生する傾向があり、トランザクションスケジューラだけではその発生を防ぐことはできません。
たとえば、リレーショナルデータベースシステムはさまざまなロックを使用して、トランザクションのACIDプロパティを保証します。
使用しているリレーショナルデータベースシステムに関係なく、変更時には常にロックが取得されます(たとえば、 UPDATE
DELETE
、特定のテーブルレコードをまたはなど)ます。現在実行中のトランザクションによって変更された行をロックしないと、原子性が損なわれます。
私はで説明したように、この記事、デッドロックは、2つの同時トランザクションが以下の図に示すように、ロックを解除するために他のための各待ちため進捗状況を作ることができないときに起こります。
どちらのトランザクションもロック取得フェーズにあるため、どちらも次のトランザクションを取得する前にロックを解放します。
ロックに依存する同時実行制御アルゴリズムを使用している場合は、常にデッドロック状態で実行されるリスクがあります。デッドロックは、データベースシステムだけでなく、あらゆる同時実行環境で発生する可能性があります。
たとえば、マルチスレッドプログラムは、2つ以上のスレッドが以前に取得されたロックを待機していて、どのスレッドも進行できない場合、デッドロックする可能性があります。これがJavaアプリケーションで発生した場合、JVMはスレッドにその実行を停止させ、そのロックを解放させることはできません。
場合であってもThread
、クラスが公開stop
メソッドを、スレッドが停止した後、オブジェクトが不整合な状態のままになる可能性があるため、Java 1.1以降、そのメソッドは非推奨になりました。代わりに、Javaはinterrupt
メソッドを定義します。これは、割り込みを受けたスレッドが割り込みを無視してその実行を継続できるため、ヒントとして機能します。
このため、Javaアプリケーションはデッドロック状態から回復することはできません。デッドロックが発生しないようにロック取得要求を順序付けるのはアプリケーション開発者の責任です。
ただし、特定のトランザクションがさらに取得したい他のロックを予測することは不可能であるため、データベースシステムは特定のロック取得順序を強制できません。ロック順序の保持はデータアクセス層の責任となり、データベースはデッドロック状態からの回復のみを支援できます。
データベースエンジンは、現在の競合グラフをスキャンしてロック待機サイクル(デッドロックが原因)を探す別のプロセスを実行します。サイクルが検出されると、データベースエンジンは1つのトランザクションを選択して中止し、そのロックを解放して、他のトランザクションが進行できるようにします。
JVMとは異なり、データベーストランザクションは、アトミックな作業単位として設計されています。したがって、ロールバックによってデータベースは一貫した状態になります。
このトピックの詳細については、こちらの記事もご覧ください。
本質的にミューテックスはロックであり、共有リソースへの保護されたアクセスを提供します。Linuxでは、スレッドミューテックスのデータ型はpthread_mutex_tです。使用する前に初期化してください。
共有リソースにアクセスするには、ミューテックスをロックする必要があります。mutexがすでにロックされている場合、呼び出しはmutexがロック解除されるまでスレッドをブロックします。共有リソースへのアクセスが完了したら、それらのロックを解除する必要があります。
全体として、いくつかの未記述の基本原則があります。
共有リソースを使用する前にロックを取得してください。
可能な限り短い時間でロックを保持する。
スレッドがエラーを返した場合、ロックを解放します。