回答:
http://en.wikipedia.org/wiki/Deadlockから取得:
並行コンピューティングでは、デッドロックは、アクションのグループの各メンバーが他のメンバーがロックを解放するのを待っている状態です
ライブロックはデッドロックに似て、ライブロックに関わるプロセスの状態が絶えず相互に関連して変化することを除いて、どれも進んでいません。Livelockはリソース不足の特殊なケースです。一般的な定義は、特定のプロセスが進行していないことのみを示しています。
現実世界のライブロックの例は、2人の人が狭い廊下で出会い、それぞれが脇に寄って他の人を通過させて礼儀正しくしようとするときに発生しますが、どちらも繰り返し移動するため、進行せずに左右に動いてしまいます同時に同じ方法。
ライブロックは、デッドロックを検出して回復するいくつかのアルゴリズムのリスクです。複数のプロセスがアクションを起こす場合、デッドロック検出アルゴリズムが繰り返しトリガーされる可能性があります。これは、1つのプロセス(ランダムに、または優先的に選択される)のみがアクションを実行するようにすることで回避できます。
多くの場合、スレッドは別のスレッドのアクションに応答して動作します。他のスレッドのアクションが別のスレッドのアクションへの応答でもある場合、ライブロックが発生する可能性があります。
デッドロックと同様に、ライブロックされたスレッドはそれ以上処理を進めることができません。しかし、スレッドがブロックされていない -彼らは単にされている作業を再開するためにお互いに応えすぎて忙しいです。これは廊下で2人がすれ違うことを試みていることに相当します。アルフォンスが左に移動してガストンを通過させ、ガストンが右に移動してアルフォンスを通過させます。彼らがまだお互いを遮っているのを見て、アルフォンスは右に移動し、ガストンは左に移動します。彼らはまだお互いをブロックしている、などなど...
ライブロックとデッドロックの主な違いは、スレッドがブロックされないことです。代わりに、スレッドは互いに継続的に応答しようとします。
この画像では、両方の円(スレッドまたはプロセス)が左右に移動することにより、他の円にスペースを与えようとします。しかし、これ以上先に進むことはできません。
ここにあるすべてのコンテンツと例は
オペレーティングシステム:内部構造と設計原則
ウィリアム・ストーリングス
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;
[...]以下の一連のイベントを検討してください:
このシーケンスは無期限に拡張でき、どちらのプロセスもクリティカルセクションに入ることができません。厳密に言えば、これはデッドロックではありません。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つの糞が走る機会を得ることのない状況。純粋な不運によって、またはその特性の一部(たとえば、優先度が低い)。
スピンロック:ロックが解放されるのを待つコストを回避する手法。
DEADLOCK デッドロックは、満たされない条件をタスクが無期限に待機する状態です-タスクは共有リソースに対する排他的な制御を要求します-タスクは他のリソースが解放されるのを待機している間リソースを保持します-タスクはリソースを強制的に書き換えることができません-循環待機状態あり
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
ループと以降commonVar
でtrue
、今、それがロックを保持することが可能です。そうして、synchronised
ブロックを実行し、にcommonVar
戻しfalse
ます。さて、スレッドAは、再び、それは、それは新しいCPUウィンドウの取得だっ終了することについてのwhile
ループをが、スレッドBが戻ったばかりに、それを設定しているfalse
サイクルが何度も繰り返されるので、。スレッドは何かを行います(従来の意味でブロックされないため)が、ほとんど何もしません。
ライブロックが必ずしもここに表示される必要はないことも言及しておくとよいでしょう。synchronised
ブロックの実行が完了すると、スケジューラは他のスレッドを優先すると想定しています。ほとんどの場合、それはヒットするのが難しい予想であり、内部で起こっている多くのことに依存していると思います。