Antworten:
Entnommen aus http://en.wikipedia.org/wiki/Deadlock :
Beim gleichzeitigen Rechnen ist ein Deadlock ein Zustand, in dem jedes Mitglied einer Gruppe von Aktionen darauf wartet, dass ein anderes Mitglied eine Sperre aufhebt
Ein Livelock ähnelt einem Deadlock, mit der Ausnahme, dass sich die Zustände der Prozesse, die an dem Livelock beteiligt sind, ständig in Bezug aufeinander ändern, ohne dass Fortschritte erzielt werden. Livelock ist ein Sonderfall des Ressourcenmangels. Die allgemeine Definition besagt nur, dass ein bestimmter Prozess nicht fortschreitet.
Ein reales Beispiel für Viehzucht ist, wenn sich zwei Menschen in einem engen Korridor treffen und jeder versucht, höflich zu sein, indem er sich zur Seite bewegt, um den anderen passieren zu lassen. Am Ende schwanken sie jedoch hin und her, ohne Fortschritte zu machen, weil sie sich beide wiederholt bewegen auf die gleiche Weise zur gleichen Zeit.
Livelock ist ein Risiko bei einigen Algorithmen, die Deadlocks erkennen und beheben. Wenn mehr als ein Prozess aktiv wird, kann der Deadlock-Erkennungsalgorithmus wiederholt ausgelöst werden. Dies kann vermieden werden, indem sichergestellt wird, dass nur ein Prozess (zufällig oder nach Priorität ausgewählt) Maßnahmen ergreift.
Ein Thread reagiert häufig auf die Aktion eines anderen Threads. Wenn die Aktion des anderen Threads auch eine Antwort auf die Aktion eines anderen Threads ist, kann dies zu einem Livelock führen.
Wie beim Deadlock können Livelock-Threads keine weiteren Fortschritte erzielen . Die Threads sind jedoch nicht blockiert - sie sind einfach zu beschäftigt, um aufeinander zu reagieren, um die Arbeit fortzusetzen . Dies ist vergleichbar mit zwei Personen, die versuchen, sich in einem Korridor zu überholen: Alphonse bewegt sich zu seiner Linken, um Gaston passieren zu lassen, während Gaston sich zu seiner Rechten bewegt, um Alphonse passieren zu lassen. Als Alphonse sieht, dass sie sich immer noch gegenseitig blockieren, bewegt er sich zu seiner Rechten, während Gaston sich zu seiner Linken bewegt. Sie blockieren sich immer noch und so weiter ...
Der Hauptunterschied zwischen Livelock und Deadlock besteht darin, dass Threads nicht blockiert werden, sondern kontinuierlich versuchen, aufeinander zu reagieren.
In diesem Bild versuchen beide Kreise (Threads oder Prozesse), dem anderen Raum zu geben, indem sie sich nach links und rechts bewegen. Aber sie können sich nicht weiter bewegen.
Alle Inhalte und Beispiele hier stammen von
Betriebssysteme: Interna und Konstruktionsprinzipien
William Stallings
8º Edition
Deadlock : Eine Situation, in der zwei oder mehr Prozesse nicht fortgesetzt werden können, weil jeder darauf wartet, dass der andere etwas unternimmt.
Betrachten Sie beispielsweise zwei Prozesse, P1 und P2, und zwei Ressourcen, R1 und R2. Angenommen, jeder Prozess benötigt Zugriff auf beide Ressourcen, um einen Teil seiner Funktion auszuführen. Dann ist folgende Situation möglich: Das Betriebssystem weist P2 R1 und R2 P1 zu. Jeder Prozess wartet auf eine der beiden Ressourcen. Keiner von beiden gibt die Ressource frei, die er bereits besitzt, bis er die andere Ressource erworben und die Funktion ausgeführt hat, die beide Ressourcen erfordert. Die beiden Prozesse sind festgefahren
Livelock : Eine Situation, in der zwei oder mehr Prozesse ihren Zustand als Reaktion auf Änderungen in den anderen Prozessen kontinuierlich ändern, ohne nützliche Arbeit zu leisten:
Hunger : Eine Situation, in der ein ausführbarer Prozess vom Scheduler auf unbestimmte Zeit übersehen wird. Obwohl es fortfahren kann, wird es nie ausgewählt.
Angenommen, drei Prozesse (P1, P2, P3) erfordern jeweils einen periodischen Zugriff auf die Ressource R. Betrachten Sie die Situation, in der P1 im Besitz der Ressource ist und sowohl P2 als auch P3 verzögert sind und auf diese Ressource warten. Wenn P1 seinen kritischen Abschnitt verlässt, sollte entweder P2 oder P3 Zugriff auf R gewährt werden. Angenommen, das Betriebssystem gewährt Zugriff auf P3 und P1 benötigt erneut Zugriff, bevor P3 seinen kritischen Abschnitt abschließt. Wenn das Betriebssystem nach Abschluss von P3 Zugriff auf P1 gewährt und anschließend abwechselnd Zugriff auf P1 und P3 gewährt, kann P2 auf unbestimmte Zeit der Zugriff auf die Ressource verweigert werden, obwohl keine Deadlock-Situation vorliegt.
ANHANG A - THEMEN DER KONKURRENZ
Deadlock-Beispiel
Wenn beide Prozesse ihre Flags auf true setzen, bevor einer die while-Anweisung ausgeführt hat, wird jeder denken, dass der andere seinen kritischen Abschnitt betreten hat, was zu einem Deadlock führt.
/* 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 Beispiel
/* 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;
[...] betrachten die folgende Abfolge von Ereignissen:
Diese Sequenz kann auf unbestimmte Zeit verlängert werden, und keiner der Prozesse kann in seinen kritischen Abschnitt eintreten. Genau genommen ist dies kein Deadlock , da jede Änderung der relativen Geschwindigkeit der beiden Prozesse diesen Zyklus unterbricht und es einem ermöglicht, den kritischen Abschnitt zu betreten. Dieser Zustand wird als Viehbestand bezeichnet . Denken Sie daran, dass ein Deadlock auftritt, wenn eine Reihe von Prozessen ihre kritischen Abschnitte eingeben möchte, aber kein Prozess erfolgreich sein kann. Mit livelock gibt es mögliche Ausführungssequenzen, die erfolgreich sind, aber es ist auch möglich, eine oder mehrere Ausführungssequenzen zu beschreiben, in denen kein Prozess jemals in seinen kritischen Abschnitt eintritt.
Kein Inhalt mehr aus dem Buch.
Und was ist mit Spinlocks?
Spinlock ist eine Technik, um die Kosten des Betriebssystemsperrmechanismus zu vermeiden. Normalerweise würden Sie Folgendes tun:
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
Ein Problem tritt auf, wenn es beginLock()
viel mehr kostet als doSomething()
. Stellen Sie sich sehr übertrieben vor, was passiert, wenn die beginLock
Kosten 1 Sekunde betragen, aber doSomething
nur 1 Millisekunde kosten.
In diesem Fall würden Sie vermeiden, 1 Sekunde lang behindert zu werden, wenn Sie 1 Millisekunde warten würden.
Warum beginLock
würde das so viel kosten? Wenn die Sperre frei ist, kostet sie nicht viel (siehe https://stackoverflow.com/a/49712993/5397116 ), aber wenn die Sperre nicht frei ist, "friert" das Betriebssystem Ihren Thread ein, richten Sie einen Mechanismus ein, um Sie zu wecken Wenn das Schloss freigegeben ist, wecken Sie Sie in Zukunft erneut.
All dies ist viel teurer als einige Schleifen, die das Schloss überprüfen. Deshalb ist es manchmal besser, einen "Spinlock" zu machen.
Zum Beispiel:
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;
}
}
Wenn Ihre Implementierung nicht vorsichtig ist, können Sie auf Livelock fallen und die gesamte CPU für den Sperrmechanismus ausgeben.
Siehe auch:
https://preshing.com/20120226/roll-your-own-lightweight-mutex/
Ist meine Spin-Lock-Implementierung korrekt und optimal?
Zusammenfassung :
Deadlock : Situation, in der niemand Fortschritte macht, nichts tut (schlafen, warten usw.). Die CPU-Auslastung ist gering.
Livelock : Situation, in der niemand Fortschritte macht, aber die CPU für den Sperrmechanismus und nicht für Ihre Berechnung aufgewendet wird.
Hunger: Situation, in der ein Prozess nie die Chance bekommt zu rennen; durch reines Pech oder durch einen Teil seines Eigentums (zum Beispiel niedrige Priorität);
Spinlock : Technik zur Vermeidung der Kosten, die darauf warten, dass das Schloss freigegeben wird.
DEADLOCK Deadlock ist eine Bedingung, bei der eine Aufgabe auf unbestimmte Zeit auf Bedingungen wartet, die niemals erfüllt werden können - Aufgabe beansprucht die ausschließliche Kontrolle über gemeinsam genutzte Ressourcen - Aufgabe hält Ressourcen, während auf die Freigabe anderer Ressourcen gewartet wird - Aufgaben können nicht gezwungen werden, Ressourcen freizugeben - ein zirkuläres Warten Bedingung besteht
LIVELOCK Livelock-Bedingungen können auftreten, wenn zwei oder mehr Aufgaben von einer Ressource abhängen und diese verwenden. Dies führt zu einer zirkulären Abhängigkeitsbedingung, bei der diese Aufgaben für immer ausgeführt werden, wodurch die Ausführung aller Aufgaben mit niedrigerer Priorität blockiert wird (bei diesen Aufgaben mit niedrigerer Priorität tritt eine Bedingung auf, die als Hunger bezeichnet wird).
Vielleicht veranschaulichen diese beiden Beispiele den Unterschied zwischen einem Deadlock und einem Livelock:
Java-Beispiel für einen Deadlock:
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");
}
}
}
Beispielausgabe:
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-Beispiel für ein Livelock:
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
}
}
}
Beispielausgabe:
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
...
Beide Beispiele zwingen die Fäden, die Schlösser in unterschiedlicher Reihenfolge zu erwerben. Während der Deadlock auf das andere Schloss wartet, wartet das Livelock nicht wirklich - es versucht verzweifelt, das Schloss zu erwerben, ohne die Chance zu haben, es zu bekommen. Jeder Versuch verbraucht CPU-Zyklen.
Stellen Sie sich vor, Sie haben Thread A und Thread B. Sie befinden sich beide synchronised
im selben Objekt und in diesem Block befindet sich eine globale Variable, die beide aktualisiert werden.
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
}
}
}
Wenn also Thread A in die while
Schleife eintritt und die Sperre hält, tut er das, was er tun muss, und setzt commonVar
auf true
. Dann Thread B kommt, tritt in der while
Schleife , und da commonVar
ist true
jetzt ist es in der Lage , das Schloss zu halten. Dies geschieht, führt den synchronised
Block aus und setzt commonVar
zurück zu false
. Jetzt erhält Thread A wieder sein neues CPU-Fenster. Er wollte gerade die while
Schleife verlassen, aber Thread B hat es gerade zurückgesetzt false
, sodass sich der Zyklus erneut wiederholt. Threads tun etwas (damit sie nicht im herkömmlichen Sinne blockiert werden), aber für so ziemlich nichts.
Es ist vielleicht auch schön zu erwähnen, dass Vieh nicht unbedingt hier erscheinen muss. Ich gehe davon aus, dass der Scheduler den anderen Thread bevorzugt, sobald die synchronised
Ausführung des Blocks abgeschlossen ist. Die meiste Zeit denke ich, dass es eine schwer zu treffende Erwartung ist und von vielen Dingen abhängt, die unter der Haube passieren.