Un buon esempio di livelock?


141

Capisco cos'è il livelock, ma mi chiedevo se qualcuno ne avesse un buon esempio basato sul codice? E per codice, non intendo "due persone che cercano di superarsi in un corridoio". Se lo rileggessi, perderò il mio pranzo.


96
Che ne dici di una simulazione software di due persone che cercano di superarsi in un corridoio?
1800 INFORMAZIONI

36
Vi maledicono! Ho perso il mio pranzo!
Alex Miller,

3
Stranamente appropriato: seuss.wikia.com/wiki/The_Zax
NotMe


4
Due persone che cercano di superarsi in un corridoio: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Risposte:


119

Ecco un semplice esempio Java di livelock in cui marito e moglie stanno cercando di mangiare la zuppa, ma hanno solo un cucchiaio tra di loro. Ogni coniuge è troppo educato e passerà il cucchiaio se l'altro non ha ancora mangiato.

public class Livelock {
    static class Spoon {
        private Diner owner;
        public Spoon(Diner d) { owner = d; }
        public Diner getOwner() { return owner; }
        public synchronized void setOwner(Diner d) { owner = d; }
        public synchronized void use() { 
            System.out.printf("%s has eaten!", owner.name); 
        }
    }

    static class Diner {
        private String name;
        private boolean isHungry;

        public Diner(String n) { name = n; isHungry = true; }       
        public String getName() { return name; }
        public boolean isHungry() { return isHungry; }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                // Don't have the spoon, so wait patiently for spouse.
                if (spoon.owner != this) {
                    try { Thread.sleep(1); } 
                    catch(InterruptedException e) { continue; }
                    continue;
                }                       

                // If spouse is hungry, insist upon passing the spoon.
                if (spouse.isHungry()) {                    
                    System.out.printf(
                        "%s: You eat first my darling %s!%n", 
                        name, spouse.getName());
                    spoon.setOwner(spouse);
                    continue;
                }

                // Spouse wasn't hungry, so finally eat
                spoon.use();
                isHungry = false;               
                System.out.printf(
                    "%s: I am stuffed, my darling %s!%n", 
                    name, spouse.getName());                
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        final Diner husband = new Diner("Bob");
        final Diner wife = new Diner("Alice");

        final Spoon s = new Spoon(husband);

        new Thread(new Runnable() { 
            public void run() { husband.eatWith(s, wife); }   
        }).start();

        new Thread(new Runnable() { 
            public void run() { wife.eatWith(s, husband); } 
        }).start();
    }
}

6
Anche il getOwnermetodo non deve essere sincronizzato? Da Java efficace "la sincronizzazione non ha alcun effetto a meno che sia in lettura che in scrittura ".
Sanghyun Lee,

Non dovrebbe usare Thread.join()piuttosto che Thread.sleep(), perché vuole aspettare che il coniuge faccia il suo pasto?
Solace,

cosa dovremmo fare per superare il problema del livelock in questo esempio particolare?
Thor,

1
Il getOwnermetodo deve essere sincronizzato poiché anche se setOwnerè sincronizzato, ciò non garantisce che il thread che utilizza getOwner(o accede ownerdirettamente al campo ) vedrà le modifiche apportate dall'altro thread in esecuzione setOwner. Questo video lo spiega molto attentamente: youtube.com/watch?v=WTVooKLLVT8
Timofey,

2
Non è necessario utilizzare la synchronized parola chiave per il setOwnermetodo, poiché la lettura e la scrittura sono azioni atomiche per la variabile di riferimento.
atiqkhaled il

75

A parte i commenti fluttuanti, un esempio noto è il codice che tenta di rilevare e gestire situazioni di deadlock. Se due thread rilevano un deadlock e provano a "farsi da parte" l'uno per l'altro, senza cura finiranno per rimanere bloccati in un loop sempre "facendosi da parte" e non riuscendo mai a muoversi in avanti.

Per "passo da parte" intendo che avrebbero rilasciato il blocco e tentato di far acquisire l'altro. Potremmo immaginare la situazione con due thread che fanno questo (pseudocodice):

// thread 1
getLocks12(lock1, lock2)
{
  lock1.lock();
  while (lock2.locked())
  {
    // attempt to step aside for the other thread
    lock1.unlock();
    wait();
    lock1.lock();
  }
  lock2.lock();
}

// thread 2
getLocks21(lock1, lock2)
{
  lock2.lock();
  while (lock1.locked())
  {
    // attempt to step aside for the other thread
    lock2.unlock();
    wait();
    lock2.lock();
  }
  lock1.lock();
}

A parte le condizioni di gara, quello che abbiamo qui è una situazione in cui entrambi i thread, se entrano contemporaneamente, finiranno per correre nel circuito interno senza procedere. Ovviamente questo è un esempio semplificato. Una soluzione naiiva sarebbe quella di mettere una sorta di casualità nel tempo che i thread avrebbero atteso.

La soluzione corretta è rispettare sempre l' erarchia dei lucchetti . Scegli un ordine in cui acquisisci i lucchetti e segui quello. Ad esempio, se entrambi i thread acquisiscono sempre lock1 prima di lock2, allora non c'è possibilità di deadlock.


Sì, lo capisco. Sto cercando un esempio di codice reale di tale. La domanda è: cosa significa "fare un passo indietro" e come produce un tale scenario.
Alex Miller,

Capisco che questo sia un esempio inventato, ma è probabile che questo possa portare a un livelock? Non sarebbe molto più probabile che alla fine si aprisse una finestra in cui una funzione potesse afferrare entrambi, a causa delle incoerenze nel tempo in cui i thread sono ad alta voce da eseguire e quando sono programmati.
DubiousPusher,

Sebbene non sia un livelock stabile perché alla fine ne usciranno ovviamente, penso che si adatti abbastanza bene alla descrizione
1800 INFORMAZIONI

Esempio eccellente e significativo.
alecov,

7

Poiché non esiste alcuna risposta contrassegnata come risposta accettata, ho tentato di creare un esempio di blocco live;

Il programma originale è stato scritto da me nell'aprile 2012 per apprendere vari concetti di multithreading. Questa volta l'ho modificato per creare deadlock, condizioni di gara, livelock ecc.

Quindi capiamo prima l'istruzione del problema;

Problema di Cookie Maker

Ci sono alcuni contenitori per ingredienti: ChocoPowederContainer , WheatPowderContainer . CookieMaker prende una certa quantità di polvere dai contenitori degli ingredienti per cuocere un biscotto . Se un produttore di cookie trova un contenitore vuoto, controlla un altro contenitore per risparmiare tempo. E aspetta che Filler riempia il contenitore richiesto. C'è un Filler che controlla il contenitore a intervalli regolari e riempie una quantità se un contenitore ne ha bisogno.

Si prega di controllare il codice completo su github ;

Lascia che ti spieghi la tua implementazione in breve.

  • Inizio Filler come thread demone. Quindi continuerà a riempire i contenitori a intervalli regolari. Per riempire prima un contenitore blocca il contenitore -> controlla se ha bisogno di polvere -> riempilo -> segnala tutti i produttori che lo stanno aspettando -> sblocca contenitore.
  • Creo CookieMaker e imposto che possa cuocere fino a 8 cookie in parallelo. E inizio 8 thread per cuocere i biscotti.
  • Ogni thread del creatore crea 2 thread secondari richiamabili per prelevare polvere dai contenitori.
  • il sottoprocesso prende un blocco su un contenitore e controlla se ha abbastanza polvere. In caso contrario, attendere qualche tempo. Una volta che Filler riempie il contenitore, prende la polvere e sblocca il contenitore.
  • Ora completa altre attività come: fare il composto, cuocere ecc.

Diamo un'occhiata al codice:

CookieMaker.java

private Integer getMaterial(final Ingredient ingredient) throws Exception{
        :
        container.lock();
        while (!container.getIngredient(quantity)) {
            container.empty.await(1000, TimeUnit.MILLISECONDS);
            //Thread.sleep(500); //For deadlock
        }
        container.unlock();
        :
}

IngredientContainer.java

public boolean getIngredient(int n) throws Exception {
    :
    lock();
    if (quantityHeld >= n) {
        TimeUnit.SECONDS.sleep(2);
        quantityHeld -= n;
        unlock();
        return true;
    }
    unlock();
    return false;
}

Tutto funziona bene fino a quando Filler sta riempiendo i contenitori. Ma se dimentico di avviare il riempimento, o il riempimento va in congedo inaspettato, i sottoprocessi continuano a cambiare i loro stati per consentire ad altri produttori di andare a controllare il contenitore.

Ho anche creato un demone ThreadTracer che controlla gli stati dei thread e i deadlock. Questo l'output dalla console;

2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:RUNNABLE, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
WheatPowder Container has 0 only.
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:RUNNABLE]
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]

Noterai che i sottoprocessi cambiano il loro stato e aspettano.


4

Un esempio reale (anche se senza codice esatto) sono due processi concorrenti che si bloccano dal vivo nel tentativo di correggere un deadlock del server SQL, con ciascun processo che utilizza lo stesso algoritmo di attesa-tentativo per riprovare. Mentre è la fortuna del tempismo, ho visto che ciò accade su macchine separate con caratteristiche prestazionali simili in risposta a un messaggio aggiunto a un argomento EMS (ad es. Salvataggio di un aggiornamento di un singolo oggetto grafico più di una volta) e non essere in grado di controllare l'ordine di blocco.

Una buona soluzione in questo caso sarebbe quella di avere consumatori in concorrenza (impedire l'elaborazione duplicata il più in alto possibile nella catena partizionando il lavoro su oggetti non correlati).

Una soluzione meno desiderabile (ok, dirty-hack) è quella di rompere in anticipo i tempi sfortunati (tipo di differenze di forza nell'elaborazione) o romperli dopo un deadlock usando algoritmi diversi o qualche elemento di casualità. Ciò potrebbe avere ancora problemi perché è possibile che l'ordine di blocco sia "appiccicoso" per ogni processo e ciò richiede un certo minimo di tempo non preso in considerazione nel tentativo di attesa.

Un'altra soluzione (almeno per SQL Server) è quella di provare un diverso livello di isolamento (ad esempio un'istantanea).


2

Ho codificato l'esempio di 2 persone che passano in un corridoio. I due thread si eviteranno non appena si renderanno conto che le loro direzioni sono uguali.

public class LiveLock {
    public static void main(String[] args) throws InterruptedException {
        Object left = new Object();
        Object right = new Object();
        Pedestrian one = new Pedestrian(left, right, 0); //one's left is one's left
        Pedestrian two = new Pedestrian(right, left, 1); //one's left is two's right, so have to swap order
        one.setOther(two);
        two.setOther(one);
        one.start();
        two.start();
    }
}

class Pedestrian extends Thread {
    private Object l;
    private Object r;
    private Pedestrian other;
    private Object current;

    Pedestrian (Object left, Object right, int firstDirection) {
        l = left;
        r = right;
        if (firstDirection==0) {
            current = l;
        }
        else {
            current = r;
        }
    }

    void setOther(Pedestrian otherP) {
        other = otherP;
    }

    Object getDirection() {
        return current;
    }

    Object getOppositeDirection() {
        if (current.equals(l)) {
            return r;
        }
        else {
            return l;
        }
    }

    void switchDirection() throws InterruptedException {
        Thread.sleep(100);
        current = getOppositeDirection();
        System.out.println(Thread.currentThread().getName() + " is stepping aside.");
    }

    public void run() {
        while (getDirection().equals(other.getDirection())) {
            try {
                switchDirection();
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
} 

2

Versione C # del codice jelbourn:

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace LiveLockExample
{
    static class Program
    {
        public static void Main(string[] args)
        {
            var husband = new Diner("Bob");
            var wife = new Diner("Alice");

            var s = new Spoon(husband);

            Task.WaitAll(
                Task.Run(() => husband.EatWith(s, wife)),
                Task.Run(() => wife.EatWith(s, husband))
                );
        }

        public class Spoon
        {
            public Spoon(Diner diner)
            {
                Owner = diner;
            }


            public Diner Owner { get; private set; }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public void SetOwner(Diner d) { Owner = d; }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public void Use()
            {
                Console.WriteLine("{0} has eaten!", Owner.Name);
            }
        }

        public class Diner
        {
            public Diner(string n)
            {
                Name = n;
                IsHungry = true;
            }

            public string Name { get; private set; }

            private bool IsHungry { get; set; }

            public void EatWith(Spoon spoon, Diner spouse)
            {
                while (IsHungry)
                {
                    // Don't have the spoon, so wait patiently for spouse.
                    if (spoon.Owner != this)
                    {
                        try
                        {
                            Thread.Sleep(1);
                        }
                        catch (ThreadInterruptedException e)
                        {
                        }

                        continue;
                    }

                    // If spouse is hungry, insist upon passing the spoon.
                    if (spouse.IsHungry)
                    {
                        Console.WriteLine("{0}: You eat first my darling {1}!", Name, spouse.Name);
                        spoon.SetOwner(spouse);
                        continue;
                    }

                    // Spouse wasn't hungry, so finally eat
                    spoon.Use();
                    IsHungry = false;
                    Console.WriteLine("{0}: I am stuffed, my darling {1}!", Name, spouse.Name);
                    spoon.SetOwner(spouse);
                }
            }
        }
    }
}

1

Un esempio qui potrebbe essere l'utilizzo di un tryLock temporizzato per ottenere più di un blocco e, se non riesci a ottenerli tutti, arretrare e riprovare.

boolean tryLockAll(Collection<Lock> locks) {
  boolean grabbedAllLocks = false;
  for(int i=0; i<locks.size(); i++) {
    Lock lock = locks.get(i);
    if(!lock.tryLock(5, TimeUnit.SECONDS)) {
      grabbedAllLocks = false;

      // undo the locks I already took in reverse order
      for(int j=i-1; j >= 0; j--) {
        lock.unlock();
      }
    }
  }
}

Potrei immaginare che tale codice sarebbe problematico in quanto ci sono molti thread che si scontrano e aspettano di ottenere una serie di blocchi. Ma non sono sicuro che questo sia molto convincente per me come un semplice esempio.


1
perché questo sia un livelock avrai bisogno di un altro thread per acquisire quei blocchi in un ordine diverso. Se tutti i thread vengono utilizzati tryLockAll()con i blocchi nello locksstesso ordine, non è presente il livelock.
JaviMerino,

0

Versione Python del codice jelbourn:

import threading
import time
lock = threading.Lock()

class Spoon:
    def __init__(self, diner):
        self.owner = diner

    def setOwner(self, diner):
        with lock:
            self.owner = diner

    def use(self):
        with lock:
            "{0} has eaten".format(self.owner)

class Diner:
    def __init__(self, name):
        self.name = name
        self.hungry = True

    def eatsWith(self, spoon, spouse):
        while(self.hungry):
            if self != spoon.owner:
                time.sleep(1) # blocks thread, not process
                continue

            if spouse.hungry:
                print "{0}: you eat first, {1}".format(self.name, spouse.name)
                spoon.setOwner(spouse)
                continue

            # Spouse was not hungry, eat
            spoon.use()
            print "{0}: I'm stuffed, {1}".format(self.name, spouse.name)
            spoon.setOwner(spouse)

def main():
    husband = Diner("Bob")
    wife = Diner("Alice")
    spoon = Spoon(husband)

    t0 = threading.Thread(target=husband.eatsWith, args=(spoon, wife))
    t1 = threading.Thread(target=wife.eatsWith, args=(spoon, husband))
    t0.start()
    t1.start()
    t0.join()
    t1.join()

if __name__ == "__main__":
    main()

Bug: in use (), la stampa non viene utilizzata e, soprattutto, la bandiera affamata non è impostata su False.
RDT

0

Modifico la risposta di @jelbourn. Quando uno di loro nota che l'altro ha fame, lui (lei) dovrebbe rilasciare il cucchiaio e attendere un'altra notifica, quindi si verifica un livelock.

public class LiveLock {
    static class Spoon {
        Diner owner;

        public String getOwnerName() {
            return owner.getName();
        }

        public void setOwner(Diner diner) {
            this.owner = diner;
        }

        public Spoon(Diner diner) {
            this.owner = diner;
        }

        public void use() {
            System.out.println(owner.getName() + " use this spoon and finish eat.");
        }
    }

    static class Diner {
        public Diner(boolean isHungry, String name) {
            this.isHungry = isHungry;
            this.name = name;
        }

        private boolean isHungry;
        private String name;


        public String getName() {
            return name;
        }

        public void eatWith(Diner spouse, Spoon sharedSpoon) {
            try {
                synchronized (sharedSpoon) {
                    while (isHungry) {
                        while (!sharedSpoon.getOwnerName().equals(name)) {
                            sharedSpoon.wait();
                            //System.out.println("sharedSpoon belongs to" + sharedSpoon.getOwnerName())
                        }
                        if (spouse.isHungry) {
                            System.out.println(spouse.getName() + "is hungry,I should give it to him(her).");
                            sharedSpoon.setOwner(spouse);
                            sharedSpoon.notifyAll();
                        } else {
                            sharedSpoon.use();
                            sharedSpoon.setOwner(spouse);
                            isHungry = false;
                        }
                        Thread.sleep(500);
                    }
                }
            } catch (InterruptedException e) {
                System.out.println(name + " is interrupted.");
            }
        }
    }

    public static void main(String[] args) {
        final Diner husband = new Diner(true, "husband");
        final Diner wife = new Diner(true, "wife");
        final Spoon sharedSpoon = new Spoon(wife);

        Thread h = new Thread() {
            @Override
            public void run() {
                husband.eatWith(wife, sharedSpoon);
            }
        };
        h.start();

        Thread w = new Thread() {
            @Override
            public void run() {
                wife.eatWith(husband, sharedSpoon);
            }
        };
        w.start();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        h.interrupt();
        w.interrupt();

        try {
            h.join();
            w.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

0
package concurrently.deadlock;

import static java.lang.System.out;


/* This is an example of livelock */
public class Dinner {

    public static void main(String[] args) {
        Spoon spoon = new Spoon();
        Dish dish = new Dish();

        new Thread(new Husband(spoon, dish)).start();
        new Thread(new Wife(spoon, dish)).start();
    }
}


class Spoon {
    boolean isLocked;
}

class Dish {
    boolean isLocked;
}

class Husband implements Runnable {

    Spoon spoon;
    Dish dish;

    Husband(Spoon spoon, Dish dish) {
        this.spoon = spoon;
        this.dish = dish;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (spoon) {
                spoon.isLocked = true;
                out.println("husband get spoon");
                try { Thread.sleep(2000); } catch (InterruptedException e) {}

                if (dish.isLocked == true) {
                    spoon.isLocked = false; // give away spoon
                    out.println("husband pass away spoon");
                    continue;
                }
                synchronized (dish) {
                    dish.isLocked = true;
                    out.println("Husband is eating!");

                }
                dish.isLocked = false;
            }
            spoon.isLocked = false;
        }
    }
}

class Wife implements Runnable {

    Spoon spoon;
    Dish dish;

    Wife(Spoon spoon, Dish dish) {
        this.spoon = spoon;
        this.dish = dish;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (dish) {
                dish.isLocked = true;
                out.println("wife get dish");
                try { Thread.sleep(2000); } catch (InterruptedException e) {}

                if (spoon.isLocked == true) {
                    dish.isLocked = false; // give away dish
                    out.println("wife pass away dish");
                    continue;
                }
                synchronized (spoon) {
                    spoon.isLocked = true;
                    out.println("Wife is eating!");

                }
                spoon.isLocked = false;
            }
            dish.isLocked = false;
        }
    }
}
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.