誰かが(コードの)例でデッドロックとライブロックの違いを説明できますか?
7 に答える
http://en.wikipedia.org/wiki/Deadlockから取得:
並行コンピューティングでは、デッドロックは、アクションのグループの各メンバーが他のメンバーがロックを解放するのを待っている状態です。
ライブロックはデッドロックに似ていますが、ライブロックに関連するプロセスの状態が相互に絶えず変化し、進行しない点が異なります。Livelockは、リソース不足の特殊なケースです。一般的な定義は、特定のプロセスが進行していないことを示しているだけです。
ライブロックの実例は、狭い廊下で二人が出会って、お互いが横に移動して相手を通過させようと礼儀正しくしようとしたが、二人とも繰り返し移動するため、何も進まずに左右に揺れてしまう。同時に同じように。
Livelockは、デッドロックを検出して回復する一部のアルゴリズムのリスクです。複数のプロセスがアクションを実行する場合、デッドロック検出アルゴリズムを繰り返しトリガーできます。これは、(ランダムにまたは優先順位によって選択された)1つのプロセスのみがアクションを実行するようにすることで回避できます。
多くの場合、スレッドは別のスレッドのアクションに応答して動作します。他のスレッドのアクションが別のスレッドのアクションへの応答でもある場合、ライブロックが発生する可能性があります。
デッドロックと同様に、ライブロックされたスレッドはそれ以上進行できません。ただし、スレッドはブロックされていません。スレッドは互いに応答するのに忙しくて、作業を再開できません。これは、廊下で2人がすれ違うことを試みていることに相当します。アルフォンスは左に移動してガストンを通過させ、ガストンは右に移動してアルフォンスを通過させます。彼らがまだお互いをブロックしているのを見て、アルフォンスは彼の右に移動し、ガストンは彼の左に移動します。彼らはまだお互いをブロックしている、など...
ライブロックとデッドロックの主な違いは、スレッドがブロックされることはなく、代わりにスレッドが相互に継続的に応答しようとすることです。
この画像では、両方の円(スレッドまたはプロセス)が左右に移動して、もう一方にスペースを与えようとします。しかし、彼らはそれ以上動くことができません。
ここにあるすべてのコンテンツと例は
オペレーティングシステム:内部および設計
原則
WilliamStallings8ºEdition
デッドロック:2つ以上のプロセスが、お互いが何かをするのを待っているために続行できない状況。
たとえば、2つのプロセスP1とP2、および2つのリソースR1とR2について考えてみます。各プロセスがその機能の一部を実行するために両方のリソースにアクセスする必要があると仮定します。その場合、次の状況が発生する可能性があります。OSがR1をP2に割り当て、R2をP1に割り当てます。各プロセスは、2つのリソースのいずれかを待機しています。どちらも、他のリソースを取得して両方のリソースを必要とする機能を実行するまで、すでに所有しているリソースを解放しません。2つのプロセスがデッドロックしている
Livelock:2つ以上のプロセスが、他のプロセスの変更に応じて、有用な作業を行わずに状態を継続的に変更する状況:
飢餓:実行可能なプロセスがスケジューラーによって無期限に見落とされる状況。続行することはできますが、選択されることはありません。
3つのプロセス(P1、P2、P3)がそれぞれリソースRへの定期的なアクセスを必要とするとします。P1がリソースを所有していて、P2とP3の両方が遅延してそのリソースを待機している状況を考えてみます。P1がクリティカルセクションを出るとき、P2またはP3のいずれかがRへのアクセスを許可されている必要があります。OSがP3へのアクセスを許可し、P3がクリティカルセクションを完了する前にP1が再びアクセスを必要とするとします。OSがP3の終了後にP1へのアクセスを許可し、その後P1とP3へのアクセスを交互に許可すると、デッドロック状態がない場合でも、P2はリソースへのアクセスを無期限に拒否される可能性があります。
付録A-同時実行のトピック
デッドロックの例
どちらかがwhileステートメントを実行する前に両方のプロセスがフラグをtrueに設定すると、それぞれが他方がクリティカルセクションに入ったと見なし、デッドロックが発生します。
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]) // <- is lock 1 free?
/* do nothing */; // <- no? so I wait 1 second, for example
// and test again.
// on more sophisticated setups we can ask
// to be woken when lock 1 is freed
/* critical section*/; // <- do what we need (this will never happen)
flag[0] = false; // <- releasing our lock
/* PROCESS 1 */
flag[1] = true;
while (flag[0])
/* do nothing */;
/* critical section*/;
flag[1] = false;
Livelockの例
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]){
flag[0] = false; // <- instead of sleeping, we do useless work
// needed by the lock mechanism
/*delay */; // <- wait for a second
flag[0] = true; // <- and restart useless work again.
}
/*critical section*/; // <- do what we need (this will never happen)
flag[0] = false;
/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
flag[1] = false;
/*delay */;
flag[1] = true;
}
/* critical section*/;
flag[1] = false;
[...]次の一連のイベントを検討してください。
- P0はflag[0]をtrueに設定します。
- P1はflag[1]をtrueに設定します。
- P0はフラグ[1]をチェックします。
- P1はflag[0]をチェックします。
- P0はflag[0]をfalseに設定します。
- P1はflag[1]をfalseに設定します。
- P0はflag[0]をtrueに設定します。
- P1はflag[1]をtrueに設定します。
このシーケンスは無期限に延長される可能性があり、どちらのプロセスもそのクリティカルセクションに入ることができませんでした。厳密に言えば、これはデッドロックではありません。2つのプロセスの相対速度を変更すると、このサイクルが中断され、クリティカルセクションに入ることができるためです。この状態はライブロックと呼ばれます。デッドロックは、一連のプロセスがクリティカルセクションに入りたいが、プロセスが成功できない場合に発生することを思い出してください。livelockを使用すると、成功する実行シーケンスが可能ですが、プロセスがクリティカルセクションに入らない1つ以上の実行シーケンスを記述することもできます。
もう本の内容ではありません。
そして、スピンロックはどうですか?
スピンロックは、OSロックメカニズムのコストを回避するための手法です。通常、次のようにします。
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
beginLock()
コストが。をはるかに超えると、問題が発生し始めdoSomething()
ます。非常に誇張された言葉で言えば、1秒かかるbeginLock
が、 1ミリ秒しかかからない場合に何が起こるか想像してみてください。doSomething
この場合、1ミリ秒待っていれば、1秒間邪魔されることはありません。
なぜそんなにbeginLock
費用がかかるのでしょうか?ロックが解放されている場合はそれほど費用はかかりませんが(https://stackoverflow.com/a/49712993/5397116を参照)、ロックが解放されていない場合、OSはスレッドを「フリーズ」し、ウェイクアップするメカニズムを設定しますロックが解除されたとき、そして将来再びあなたを目覚めさせます。
これらはすべて、ロックをチェックする一部のループよりもはるかにコストがかかります。そのため、「スピンロック」を実行する方がよい場合があります。
例えば:
void beginSpinLock(lock)
{
if(lock) loopFor(1 milliseconds);
else
{
lock = true;
return;
}
if(lock) loopFor(2 milliseconds);
else
{
lock = true;
return;
}
// important is that the part above never
// cause the thread to sleep.
// It is "burning" the time slice of this thread.
// Hopefully for good.
// some implementations fallback to OS lock mechanism
// after a few tries
if(lock) return beginLock(lock);
else
{
lock = true;
return;
}
}
実装に注意を払わないと、すべてのCPUをロックメカニズムに費やして、livelockに陥る可能性があります。
以下も参照してください。
https://preshing.com/20120226/roll-your-own-lightweight-mutex/
私のスピンロックの実装は正しく最適ですか?
まとめ:
デッドロック:誰も進行せず、何もしない(睡眠、待機など)状況。CPU使用率は低くなります。
Livelock:誰も進行しないが、CPUは計算ではなくロックメカニズムに費やされている状況。
飢餓:1つのプロセスが実行される機会を決して得られない状況。純粋な不運またはその特性の一部(たとえば、優先度が低い)によって。
スピンロック:ロックが解放されるのを待つコストを回避する手法。
デッド ロックデッドロックは、タスクが決して満たすことができない条件を無期限に待機する状態です-タスクは共有リソースに対する排他的制御を要求します-タスクは他のリソースが解放されるのを待っている間リソースを保持します-タスクはリソースを解放することを強制できません-循環待機条件が存在します
LIVELOCK Livelock 状態は、2つ以上のタスクが一部のリソースに依存して使用し、それらのタスクが永久に実行され続ける循環依存状態を引き起こし、優先度の低いすべてのタスクの実行をブロックする場合に発生する可能性があります(これらの優先度の低いタスクは飢餓と呼ばれる状態になります)
おそらく、これら2つの例は、デッドロックとライブロックの違いを示しています。
Java-デッドロックの例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
}
public static void doB() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
}
}
サンプル出力:
Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2
Java-ライブロックの例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LivelockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(LivelockSample::doA, "Thread A");
Thread threadB = new Thread(LivelockSample::doB, "Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
public static void doB() {
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
}
サンプル出力:
Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...
どちらの例でも、スレッドは異なる順序でロックを取得するように強制されます。デッドロックが他のロックを待機している間、ライブロックは実際には待機しません。ロックを取得する機会がないまま、必死にロックを取得しようとします。すべての試行はCPUサイクルを消費します。
スレッドAとスレッドBがあるとします。これらは両方ともsynchronised
同じオブジェクト上にあり、このブロック内には両方とも更新しているグローバル変数があります。
static boolean commonVar = false;
Object lock = new Object;
...
void threadAMethod(){
...
while(commonVar == false){
synchornized(lock){
...
commonVar = true
}
}
}
void threadBMethod(){
...
while(commonVar == true){
synchornized(lock){
...
commonVar = false
}
}
}
したがって、スレッドAがwhile
ループに入り、ロックを保持すると、スレッドAは必要な処理を実行し、をに設定commonVar
しtrue
ます。次に、スレッドBが入り、ループwhile
に入ります。これで、ロックを保持できるようになります。これにより、ブロックが実行され、に戻ります。これで、スレッドAは再び新しいCPUウィンドウを取得し、ループを終了しようとしましたが、スレッドBはそれをに戻したばかりなので、サイクルが繰り返し繰り返されます。スレッドは何かをしますが(したがって、従来の意味でブロックされません)、ほとんど何もしません。commonVar
true
synchronised
commonVar
false
while
false
また、livelockが必ずしもここに表示される必要はないことにも言及しておくとよいでしょう。synchronised
ブロックの実行が終了すると、スケジューラーは他のスレッドを優先すると想定しています。ほとんどの場合、それはヒットするのが難しい期待であり、内部で起こっている多くのことに依存していると思います。
私はちょうどいくつかの知識を共有することを計画しました。
デッドロック セット内の各スレッド/プロセスが、セット内の別のプロセスのみが引き起こす可能性のあるイベントを待機している場合、スレッド/プロセスのセットはデッドロックされます。
ここで重要なのは、別のプロセスも同じセットに含まれていることです。つまり、別のプロセスもブロックされ、誰も続行できなくなります。
プロセスにリソースへの排他的アクセスが許可されると、デッドロックが発生します。
デッドロックを発生させるには、これらの4つの条件が満たされている必要があります。
- 相互排除条件(各リソースは1つのプロセスに割り当てられます)
- 保留および待機条件(保留リソースを処理すると同時に、他のリソースに問い合わせることができます)。
- プリエンプション条件なし(以前に付与されたリソースを強制的に削除することはできません)#この条件はアプリケーションによって異なります
- 循環待機条件(2つ以上のプロセスの循環チェーンである必要があり、それぞれがチェーンの次のメンバーによって保持されているリソースを待機しています)#動的に発生します
これらの条件が見つかった場合、デッドロックなどの状況が発生した可能性があると言えます。
LiveLock
各スレッド/プロセスは同じ状態を何度も繰り返していますが、それ以上進行しません。プロセスがクリティカルセクションに入ることができないため、デッドロックに似たもの。ただし、デッドロックでは、プロセスは何もせずに待機しますが、ライブロックでは、プロセスは続行しようとしますが、プロセスは同じ状態に何度も繰り返されます。
(デッドロックされた計算では、成功する可能性のある実行シーケンスはありません。しかし、ライブロックされた計算では、成功した計算がありますが、プロセスがクリティカルセクションに入らない実行シーケンスが1つ以上あります。)
デッドロックおよびライブロックとの違い
デッドロックが発生すると、実行は行われません。しかし、ライブロックでは、いくつかの実行が発生しますが、それらの実行はクリティカルセクションに入るのに十分ではありません。