デッドロックとライブロックの違いは何ですか?


回答:


398

http://en.wikipedia.org/wiki/Deadlockから取得

並行コンピューティングでは、デッドロックは、アクションのグループの各メンバーが他のメンバーがロックを解放するのを待っている状態です

ライブロックはデッドロックに似て、ライブロックに関わるプロセスの状態が絶えず相互に関連して変化することを除いて、どれも進んでいません。Livelockはリソース不足の特殊なケースです。一般的な定義は、特定のプロセスが進行していないことのみを示しています。

現実世界のライブロックの例は、2人の人が狭い廊下で出会い、それぞれが脇に寄って他の人を通過させて礼儀正しくしようとするときに発生しますが、どちらも繰り返し移動するため、進行せずに左右に動いてしまいます同時に同じ方法。

ライブロックは、デッドロックを検出して回復するいくつかのアルゴリズムのリスクです。複数のプロセスがアクションを起こす場合、デッドロック検出アルゴリズムが繰り返しトリガーされる可能性があります。これは、1つのプロセス(ランダムに、または優先的に選択される)のみがアクションを実行するようにすることで回避できます。


8
私はすでにそれを見つけましたが、あなたが見ることができるような例はありません、とにかくありがとう
macindows

61
コード例は提供しませんが、それぞれが他のリソースが持っているリソースを待機しているが、非ブロッキングの方法で待機している2つのプロセスを考えます。それぞれが継続できないことを学習すると、保持しているリソースを解放して30秒間スリープします。その後、元のリソースを取得してから、他のプロセスが保持しているリソースを試行してから、そのままにして、再取得します。両方のプロセスが(ひどく)対処しようとしているので、これはライブロックです。
ma

4
あなたは私に同じ例を与えることができますが、事前のおかげでデッドロックがあります
macindows

32
デッドロックの例ははるかに簡単です... 2つのプロセスAとBがあり、それぞれがリソースr1とリソースr2を必要としていると仮定します。Aがr1を受け取った(またはすでに持っている)と仮定し、Bがr2を受け取った(またはすでに持っている)と仮定します。これで、タイムアウトなしで、それぞれが他のリソースを取得しようとします。AはBがr2を保持しているためブロックされ、BはAがr1を保持しているためブロックされています。各プロセスはブロックされるため、他のプロセスが必要とするリソースを解放できず、デッドロックが発生します。
ma

2
トランザクションメモリのコンテキスト内に、デッドロックとライブロックを示す素晴らしいビデオがあります:youtube.com/watch
v

78

ライロック

多くの場合、スレッドは別のスレッドのアクションに応答して動作します。他のスレッドのアクションが別のスレッドのアクションへの応答でもある場合、ライブロックが発生する可能性があります。

デッドロックと同様に、ライブロックされたスレッドはそれ以上処理を進めることできません。しかし、スレッドがブロックされていない -彼らは単にされている作業を再開するためにお互いに応えすぎて忙しいです。これは廊下で2人がすれ違うことを試みていることに相当します。アルフォンスが左に移動してガストンを通過させ、ガストンが右に移動してアルフォンスを通過させます。彼らがまだお互いを遮っているのを見て、アルフォンスは右に移動し、ガストンは左に移動します。彼らはまだお互いをブロックしている、などなど...

ライブロックデッドロックの主な違いは、スレッドがブロックされないことです。代わりに、スレッドは互いに継続的に応答しようとします。

この画像では、両方の円(スレッドまたはプロセス)が左右に移動することにより、他の円にスペースを与えようとします。しかし、これ以上先に進むことはできません。

ここに画像の説明を入力してください



1
これには名前があります。おそらく俗語だが、それでもなお:schlumperdink:P
John Red

64

ここにあるすべてのコンテンツと例は

オペレーティングシステム:内部構造と設計原則
ウィリアム・ストーリングス
8º版

デッドロック: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が再びアクセスを必要とするとします。P3の終了後にOSが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;

ライブロックの例

/* 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はflag [1]をチェックします。
  • P1はフラグ[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()ます。非常に大げさに言えば、beginLockコストが1秒で、doSomethingたった1ミリ秒のコストで何が起こるか想像してみてください。

この場合、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をロックメカニズムに費やす可能性があります。

こちらもご覧ください:

https://preshing.com/20120226/roll-your-own-lightweight-mutex/
スピンロックの実装は正しく最適ですか?

まとめ

デッドロック:誰も進行せず、何もしない状態(睡眠、待機など)。CPU使用率は低くなります。

Livelock:誰も進行しないが、CPUが計算ではなくロックメカニズムに費やされている状況。

飢餓:1つの糞が走る機会を得ることのない状況。純粋な不運によって、またはその特性の一部(たとえば、優先度が低い)。

スピンロック:ロックが解放されるのを待つコストを回避する手法。


サー、あなたがデッドロックに与えた例は、実際にはスピンロックの例です。デッドロックは、準備状態または実行状態ではなく、一部のリソースを待機している一連のプロセスがブロックされたときに発生します。しかし、この例では、それぞれが何らかのタスクを実行しています。つまり、状態を何度もチェックしています。私が間違っていたら訂正してください。
Vinay Yadav

この例はごくわずかなので、この解釈の可能性が開かれるので、違いをより明確にするために例を改善しました。お役に立てば幸いです。
Daniel Frederico Lins Leite

スピンロックについて追加していただきありがとうございます。あなたによると、スピンロックはテクニックであり、uも正当化していると私は理解しました。しかし、1つのプロセスP1がクリティカルセクションにあり、他の優先度の高いプロセスP2がP1をプリエンプトするようにスケジュールされている場合、この優先順位の逆転の問題はどうでしょうか。この場合、CPUはP2にあり、同期メカニズムはP1にあります。これは、P1が作動可能状態にあり、P2が実行状態にあるため、スピンロックと呼ばれます。ここでスピンロックが問題です。私は物事を正しくしていますか?私は複雑さを正しくすることができません。助けてください
Vinay Yadav

あなたへの私の提案は、あなたの問題をより明確に述べる別の質問を作成することです。ここで、「ユーザー空間」にいて、P1が、無限ループで実装されたSpinLockで保護された重要なセッション内にあり、そのプリエンプトされた場合。その後、P2はそれを入力しようとし、失敗して、そのタイムスライスをすべて書き込みます。ライブロックを作成しました(1つのCPUが100%になります)。(悪い使い方は、スピンロックで同期IOを保護することです。この例は簡単に試すことができます)「カーネルスペース」では、このノートが役立ちます:lxr.linux.no/linux+v3.6.6/Documentation/…
Daniel Frederico Lins Leite

説明をありがとうございました。とにかく、あなたの答えは他とは異なり非常に説明的で役に立ちました
Vinay Yadav

13

DEADLOCK デッドロックは、満たされない条件をタスクが無期限に待機する状態です-タスクは共有リソースに対する排他的な制御を要求します-タスクは他のリソースが解放されるのを待機している間リソースを保持します-タスクはリソースを強制的に書き換えることができません-循環待機状態あり

LIVELOCK Livelock 状態は、2つ以上のタスクがいくつかのリソースに依存し、それらを使用すると循環依存状態を引き起こし、それらのタスクが永久に実行を継続する場合に発生する可能性があります。


「ライブロック」タスクが「バックオフ」遅延を含むリソースアービトレーションプロトコルに従っており、結果としてほとんどの時間をスリープ状態で費やしている場合、他のタスクが不足することはありません。
greggo 2015年

8

おそらくこれらの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が、必要な2番目のロックを取得できないことに気付いたときに、ロックを解放することです。次に、それぞれ1秒間スリープし、元々あったロックを再取得してから、さらに1秒間スリープし、もう1つのロックを再度取得しようとします。したがって、それぞれのサイクルは次のようになります:1)取得-鉱山、2)睡眠、3)他のものを取得して失敗する、4)リリース-鉱山、5)睡眠、6)繰り返し。
CognizantApe

1
あなたが考えるライブロックが本当に問題を引き起こすほど長く存在しているかどうかは疑問です。次のロックを割り当てることができないときに、保持しているすべてのロックを常に放棄すると、実際にはもう待機がないため、デッドロック(およびライブロック)条件「保持して待機」が失われます。(en.wikipedia.org/wiki/Deadlock
mmirwaldt

実際、これらは私たちが議論しているライブロックであるため、デッドロック状態はありません。:私が与えた例では、所定の標準廊下例と同様であるgeeksforgeeks.org/deadlock-starvation-and-livelocken.wikibooks.org/wiki/Operating_System_Design/Concurrency/...docs.oracle.com/javase/tutorial/essential / concurrency /…
CognizantApe

0

スレッド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 が実行する必要があることを実行し、をに設定commonVartrueます。その後、スレッドBは、入って来に入り、whileループと以降commonVartrue、今、それがロックを保持することが可能です。そうして、synchronisedブロックを実行し、にcommonVar戻しfalseます。さて、スレッドAは、再び、それは、それは新しいCPUウィンドウの取得だっ終了することについてのwhileループをが、スレッドBが戻ったばかりに、それを設定しているfalseサイクルが何度も繰り返されるので、。スレッドは何かを行います(従来の意味でブロックされないため)が、ほとんど何もしません。

ライブロックが必ずしもここに表示される必要はないことも言及しておくとよいでしょう。synchronisedブロックの実行が完了すると、スケジューラは他のスレッドを優先すると想定しています。ほとんどの場合、それはヒットするのが難しい予想であり、内部で起こっている多くのことに依存していると思います。


いい例です。また、並行コンテキストで常にアトミックに読み書きする必要がある理由も示しています。whileループが同期ブロック内にある場合、上記は問題になりません。
CognizantApe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.