Qual è la differenza tra deadlock e livelock?


Risposte:


398

Tratto da http://en.wikipedia.org/wiki/Deadlock :

Nel calcolo simultaneo, un deadlock è uno stato in cui ciascun membro di un gruppo di azioni è in attesa che un altro membro rilasci un blocco

Un livelock è simile a un deadlock, tranne per il fatto che gli stati dei processi coinvolti nel livelock cambiano costantemente l'uno rispetto all'altro, nessuno progredendo. Livelock è un caso speciale di fame di risorse; la definizione generale afferma solo che un processo specifico non sta progredendo.

Un esempio del mondo reale di livelock si verifica quando due persone si incontrano in uno stretto corridoio, e ognuna cerca di essere educata spostandosi di lato per lasciare passare l'altra, ma finiscono per oscillare da un lato all'altro senza fare alcun progresso perché entrambi si muovono ripetutamente allo stesso modo allo stesso tempo.

Livelock è un rischio con alcuni algoritmi che rilevano e ripristinano da deadlock. Se interviene più di un processo, è possibile attivare ripetutamente l'algoritmo di rilevamento dei deadlock. Questo può essere evitato assicurando che solo un processo (scelto casualmente o per priorità) agisca.


8
L'ho già trovato, ma non hanno esempi lì come puoi vedere, grazie comunque
macindows

61
Non fornirò un esempio di codice, ma considererò due processi ciascuno in attesa di una risorsa che l'altro ha ma in attesa in modo non bloccante. Quando ognuno apprende che non possono continuare, rilasciano la loro risorsa trattenuta e dormono per 30 secondi, quindi recuperano la risorsa originale seguita provando la risorsa trattenuta dall'altro processo, quindi lasciata, quindi riacquistata. Dal momento che entrambi i processi stanno cercando di far fronte (solo male), questo è un livelock.
Mah,

4
puoi darmi lo stesso esempio ma con deadlock, grazie in anticipo
macindows

32
Un esempio di deadlock è molto più semplice ... si supponga che due processi A e B, e ciascuno voglia risorsa r1 e risorsa r2. Supponiamo che A riceva (o abbia già) r1 e B riceva (o abbia già) r2. Ora ognuno cerca di ottenere la risorsa dell'altro, senza alcun timeout. A è bloccato perché B tiene r2 e B è bloccato perché A tiene r1. Ogni processo è bloccato e quindi non può rilasciare la risorsa che l'altro desidera, causando deadlock.
Mah,

2
Nel contesto della memoria transazionale c'è un ottimo video che mostra deadlock e livelock: youtube.com/watch?v=_IxsOEEzf-c
BlackVegetable

78

livelock

Un thread agisce spesso in risposta all'azione di un altro thread. Se anche l'azione dell'altro thread è una risposta all'azione di un altro thread, potrebbe verificarsi un livelock.

Come nel caso del deadlock, i thread con livelock non sono in grado di fare ulteriori progressi . Tuttavia, i thread non sono bloccati : sono semplicemente troppo occupati a rispondere l'un l'altro per riprendere il lavoro . Questo è paragonabile a due persone che tentano di passarsi l'un l'altro in un corridoio: Alphonse si sposta alla sua sinistra per far passare Gaston, mentre Gaston si sposta alla sua destra per far passare Alphonse. Vedendo che si stanno ancora bloccando a vicenda, Alphonse si sposta alla sua destra, mentre Gaston si sposta alla sua sinistra. Si stanno ancora bloccando a vicenda, e così via ...

La differenza principale tra livelock e deadlock è che i thread non verranno bloccati, ma cercheranno di rispondersi continuamente.

In questa immagine, entrambi i cerchi (thread o processi) proveranno a dare spazio all'altro spostandosi a sinistra e a destra. Ma non possono muoversi oltre.

inserisci qui la descrizione dell'immagine



1
Questa cosa ha un nome. Una parola gergale forse, ma comunque: schlumperdink : P
John Red

64

Tutto il contenuto e gli esempi qui provengono da

Sistemi operativi: principi interni e di progettazione
William Stallings
8º edizione

Deadlock : una situazione in cui due o più processi non sono in grado di procedere perché ognuno è in attesa che l'altro faccia qualcosa.

Ad esempio, considera due processi, P1 e P2 e due risorse, R1 e R2. Supponiamo che ogni processo abbia bisogno di accedere ad entrambe le risorse per svolgere parte della sua funzione. Quindi è possibile avere la seguente situazione: il sistema operativo assegna R1 a P2 e R2 a P1. Ogni processo è in attesa di una delle due risorse. Nessuno dei due rilascerà la risorsa che possiede già fino a quando non ha acquisito l'altra risorsa ed eseguito la funzione che richiede entrambe le risorse. I due processi sono bloccati

Livelock : una situazione in cui due o più processi cambiano continuamente i loro stati in risposta ai cambiamenti negli altri processi senza svolgere alcun lavoro utile:

Fame : una situazione in cui un pianificatore esegue un processo a tempo indeterminato indefinitamente; sebbene sia in grado di procedere, non viene mai scelto.

Supponiamo che tre processi (P1, P2, P3) richiedano ciascuno un accesso periodico alla risorsa R. Considera la situazione in cui P1 è in possesso della risorsa e sia P2 che P3 sono in ritardo, in attesa di quella risorsa. Quando P1 esce dalla sua sezione critica, P2 o P3 dovrebbero avere accesso a R. Supponi che il sistema operativo conceda l'accesso a P3 e che P1 richieda nuovamente l'accesso prima che P3 completi la sua sezione critica. Se il sistema operativo concede l'accesso a P1 dopo che P3 è terminato e successivamente concede alternativamente l'accesso a P1 e P3, a P2 può essere indefinitamente negato l'accesso alla risorsa, anche se non si verifica una situazione di deadlock.

APPENDICE A - TEMI IN CONCORRENZA

Esempio di deadlock

Se entrambi i processi impostano i loro flag su true prima che uno dei due abbia eseguito l'istruzione while, ognuno penserà che l'altro sia entrato nella sua sezione critica, causando deadlock.

/* 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;

Esempio di 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;

[...] considera la seguente sequenza di eventi:

  • P0 imposta flag [0] su true.
  • P1 imposta flag [1] su true.
  • P0 controlla flag [1].
  • P1 controlla flag [0].
  • P0 imposta flag [0] su falso.
  • P1 imposta flag [1] su falso.
  • P0 imposta flag [0] su true.
  • P1 imposta flag [1] su true.

Questa sequenza potrebbe essere estesa indefinitamente e nessuno dei due processi potrebbe entrare nella sua sezione critica. A rigor di termini, questo non è un deadlock , perché qualsiasi alterazione della velocità relativa dei due processi interromperà questo ciclo e consentirà di entrare nella sezione critica. Questa condizione è denominata livelock . Ricorda che il deadlock si verifica quando un insieme di processi desidera accedere alle loro sezioni critiche ma nessun processo può avere successo. Con livelock , ci sono possibili sequenze di esecuzioni che hanno successo, ma è anche possibile descrivere una o più sequenze di esecuzione in cui nessun processo entra nella sua sezione critica.

Non più contenuto del libro.

E gli spinlock?

Spinlock è una tecnica per evitare il costo del meccanismo di blocco del sistema operativo. In genere si dovrebbe fare:

try
{
   lock = beginLock();
   doSomething();
}
finally
{
   endLock();
}

Un problema inizia ad apparire quando beginLock()costa molto di più doSomething(). In termini molto esagerati, immagina cosa succede quando beginLockcosta 1 secondo, ma doSomethingcosta solo 1 millisecondo.

In questo caso, se hai aspettato 1 millisecondo, eviterai di essere ostacolato per 1 secondo.

Perché beginLockcosterebbe così tanto? Se il blocco è gratuito non costa molto (vedi https://stackoverflow.com/a/49712993/5397116 ), ma se il blocco non è libero il sistema operativo "congela" il tuo thread, imposta un meccanismo per riattivarti quando il blocco viene liberato, quindi svegliarti di nuovo in futuro.

Tutto questo è molto più costoso di alcuni anelli che controllano la serratura. Ecco perché a volte è meglio fare uno "spinlock".

Per esempio:

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;
   }
}

Se la tua implementazione non è attenta, puoi cadere su livelock, spendendo tutta la CPU sul meccanismo di blocco.

Vedi anche:

https://preshing.com/20120226/roll-your-own-lightweight-mutex/
La mia implementazione del blocco spin è corretta e ottimale?

Riepilogo :

Deadlock : situazione in cui nessuno progredisce, non fa nulla (dormire, aspettare ecc.). L'utilizzo della CPU sarà basso;

Livelock : situazione in cui nessuno progredisce, ma la CPU viene spesa nel meccanismo di blocco e non nel calcolo;

La fame: situazione in cui una procura non ha mai la possibilità di correre; per pura sfortuna o per parte della sua proprietà (bassa priorità, ad esempio);

Spinlock : tecnica per evitare il costo in attesa del rilascio della serratura.


Signore, l'esempio che hai dato per Deadlock è in realtà un esempio di Spinlock. Il deadlock si verifica quando viene bloccata una serie di processi che non sono pronti o in esecuzione e in attesa di alcune risorse. Ma nel nostro esempio ognuno sta eseguendo alcuni compiti, cioè controllando la condizione ancora e ancora. Correggimi se sbaglio.
Vinay Yadav,

L'esempio è talmente minimale da dare una possibilità aperta a questa interpretazione, quindi l'ho migliorata per essere un po 'più esplicita sulla loro differenza. Spero che aiuti.
Daniel Frederico Lins Leite,

Grazie per aver aggiunto degli spinlock, secondo te gli spinlock sono una tecnica e anche tu l'hai giustificata e ho capito. Ma che dire di quel problema di inversione di priorità quando un processo P1 si trova nella sezione critica e un altro processo ad alta priorità P2 viene programmato anticipando P1, in questo caso la CPU è con P2 e il nostro meccanismo di sincronizzazione è con P1. Questo si chiama Spinlock poiché P1 è nello stato Pronto e P2 è nello stato Run . Qui lo spinlock è un problema. Sto ottenendo le cose giuste? Non sono in grado di ottenere le complessità giuste. Per favore
aiutatemi

Il mio suggerimento per te è di creare un'altra domanda affermando il tuo problema più chiaramente. Ora, se ci si trova nello "spazio utente", e P1 è all'interno di una sessione critica protetta con uno SpinLock implementato con un ciclo infinito e la sua preemptata; allora P2 proverà ad inserirlo, fallirà e brucerà tutto il suo intervallo di tempo. Hai creato un livelock (una CPU sarà al 100%). (un cattivo uso sarebbe proteggere un IO di sincronizzazione con spinlock. Puoi facilmente provare questo esempio) Sullo "spazio del kernel" forse questa nota può aiutarti: lxr.linux.no/linux+v3.6.6/Documentation/…
Daniel Frederico Lins Leite,

Grazie mille per il chiarimento. Comunque, la tua risposta è stata abbastanza descrittiva e utile a differenza di altri
Vinay Yadav

13

DEADLOCK Deadlock è una condizione in cui un'attività attende indefinitamente condizioni che non possono mai essere soddisfatte - l'attività rivendica il controllo esclusivo sulle risorse condivise - l'attività contiene risorse in attesa di altre risorse da rilasciare - le attività non possono essere costrette a spegnere le risorse - un'attesa circolare la condizione esiste

LIVELOCK Le condizioni di Livelock possono sorgere quando due o più attività dipendono e utilizzano alcune risorse che causano una condizione di dipendenza circolare in cui tali attività continuano a funzionare per sempre, bloccando così l'esecuzione di tutte le attività di livello di priorità inferiore (queste attività di priorità inferiore presentano una condizione chiamata fame)


Se le attività "livelocked" seguono i protocolli di arbitrato delle risorse che includono ritardi di "backoff" e, di conseguenza, trascorrono la maggior parte del loro tempo in stato di sospensione, le altre attività non saranno prive di fame.
Greggo,

8

Forse questi due esempi ti illustrano la differenza tra un deadlock e un livelock:


Esempio Java per un 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");
        }
    }
}

Uscita campione:

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

Esempio Java per un 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
        }
    }
}

Uscita campione:

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
...

Entrambi gli esempi costringono i fili ad acquisire le serrature in diversi ordini. Mentre il deadlock attende l'altro lock, il livelock non aspetta davvero - cerca disperatamente di acquisire il lock senza la possibilità di ottenerlo. Ogni tentativo consuma cicli di CPU.


Il codice è carino Ma l'esempio live-lock non è buono. Se un thread è bloccato su un valore o sta eseguendo il polling per una modifica del valore non è concettualmente diverso. Una semplice modifica per illustrare meglio un live-lock è che i thread A e B rilasciano i lock che hanno quando si rendono conto che non possono ottenere il secondo lock di cui hanno bisogno. Quindi dormono per un secondo ciascuno, riacquistano il blocco che avevano originariamente, quindi dormono per un altro secondo e tentano di acquisire di nuovo l'altro blocco. Quindi il ciclo per ciascuno sarebbe: 1) acquisisci-mio, 2) dormi, 3) prova ad acquisire altro e fallisci, 4) rilascia-mio, 5) dormi, 6) Ripeti.
CognizantApe

1
Dubito che i live-lock che pensi esistano davvero abbastanza a lungo da causare problemi. Quando si rinuncia sempre a tutti i blocchi che si tengono quando non è possibile allocare il blocco successivo, manca la condizione deadlock (e live-lock) "hold and wait" perché in realtà non c'è più attesa. ( en.wikipedia.org/wiki/Deadlock )
mmirwaldt,

in effetti manca la condizione di dead dead perché si tratta di lock live di cui stiamo discutendo. L'esempio che ho fornito è simile all'esempio standard di corridoio fornito: geeksforgeeks.org/deadlock-starvation-and-livelock , en.wikibooks.org/wiki/Operating_System_Design/Concurrency/… , docs.oracle.com/javase/tutorial/essential / concorrenza /…
CognizantApe

0

Immagina di avere il thread A e il thread B. Sono entrambi synchronisedsullo stesso oggetto e all'interno di questo blocco c'è una variabile globale che entrambi stanno aggiornando;

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
         }
    }
}

Quindi, quando il thread A entra nel whileloop e mantiene il blocco, fa quello che deve fare e imposta il commonVarto true. Poi filo B entra, entra nel whileciclo e poiché commonVarè trueora, è in grado di mantenere il blocco. Lo fa, esegue il synchronisedblocco e commonVartorna a false. Ora, il thread A ottiene nuovamente la sua nuova finestra della CPU, stava per uscire dal whileloop ma il thread B lo ha appena ripristinato false, quindi il ciclo si ripete di nuovo. Le discussioni fanno qualcosa (quindi non sono bloccate nel senso tradizionale) ma praticamente per niente.

Forse è anche bello ricordare che il livelock non deve necessariamente apparire qui. Suppongo che lo scheduler favorisca l'altro thread una volta synchronisedterminato l'esecuzione del blocco. Il più delle volte, penso che sia un'aspettativa difficile da colpire e dipende da molte cose che accadono sotto il cofano.


Bell'esempio Illustra anche perché dovresti sempre leggere e scrivere atomicamente in un contesto concorrente. Se i loop while fossero all'interno dei blocchi di sincronizzazione, quanto sopra non sarebbe un problema.
Cognizant Ap
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.