Bon exemple de livelock?


141

Je comprends ce qu'est livelock, mais je me demandais si quelqu'un en avait un bon exemple basé sur le code? Et par code, je ne veux pas dire "deux personnes essayant de se croiser dans un couloir". Si je relis cela, je perdrai mon déjeuner.


96
Que diriez-vous d'une simulation logicielle de deux personnes essayant de se croiser dans un couloir?
1800 INFORMATION

36
Vous maudit! J'ai perdu mon déjeuner!
Alex Miller

3
Étrangement approprié: seuss.wikia.com/wiki/The_Zax
NotMe

Blague connexe pour les curieux: codingarchitect.wordpress.com/2006/01/18/…
Jorjon

4
Deux personnes essayant de se dépasser dans un couloir: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Réponses:


119

Voici un exemple Java très simple de livelock où un mari et une femme essaient de manger de la soupe, mais n'ont qu'une cuillère entre eux. Chaque conjoint est trop poli et passera la cuillère si l'autre n'a pas encore mangé.

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
La getOwnerméthode ne doit-elle pas être synchronisée également? From Effective Java "la synchronisation n'a aucun effet sauf en lecture et en écriture ".
Sanghyun Lee

Ne devrait-il pas utiliser Thread.join()plutôt que Thread.sleep(), parce qu'il veut attendre que le conjoint fasse son repas?
Solace

que devons-nous faire pour surmonter le problème du vivelock dans cet exemple particulier?
Thor

1
La getOwnerméthode doit être synchronisée car même si le setOwnerest synchronisé, cela ne garantit pas que le thread utilisant getOwner(ou accédant ownerdirectement au champ ) verra les modifications apportées par l'autre thread s'exécuter setOwner. Cette vidéo explique cela très attentivement: youtube.com/watch?v=WTVooKLLVT8
Timofey

2
Vous n'avez pas besoin d'utiliser un synchronized mot-clé pour la setOwnerméthode, car la lecture et l'écriture sont des actions atomiques pour la variable de référence.
atiqkhaled

75

Mis à part les commentaires désinvoltes, un exemple connu est celui du code qui tente de détecter et de gérer les situations de blocage. Si deux threads détectent une impasse, et essaient de "s'écarter" l'un pour l'autre, sans se soucier ils finiront par se retrouver coincés dans une boucle toujours "écartés" et ne parvenant jamais à avancer.

Par «pas de côté», je veux dire qu'ils libéreraient le verrou et tenteraient de laisser l'autre l'acquérir. Nous pourrions imaginer la situation avec deux threads faisant cela (pseudocode):

// 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();
}

Mis à part les conditions de course, ce que nous avons ici est une situation où les deux threads, s'ils entrent en même temps, finiront par courir dans la boucle intérieure sans continuer. C'est évidemment un exemple simplifié. Une solution naïve serait de mettre une sorte d'aléatoire dans le temps d'attente des threads.

La solution appropriée est de toujours respecter l' héritier des verrous . Choisissez une commande dans laquelle vous acquérez les serrures et respectez-la. Par exemple, si les deux threads acquièrent toujours lock1 avant lock2, alors il n'y a aucune possibilité de blocage.


Ouais, je comprends ça. Je recherche un exemple de code réel de ce type. La question est de savoir ce que signifie «se retirer» et comment produit-il un tel scénario.
Alex Miller

Je comprends que c'est un exemple artificiel, mais est-il probable que cela puisse conduire à un blocage de la vie? Ne serait-il pas beaucoup plus probable qu'une fenêtre s'ouvrirait éventuellement là où une fonction pourrait saisir les deux, en raison d'incohérences entre le temps d'exécution des threads et le moment où ils sont planifiés.
DubiousPusher

Bien que ce ne soit pas un livelock stable car ils finiront évidemment par en sortir, je pense que cela correspond assez bien à la description
1800 INFORMATIONS

Excellent exemple significatif.
alecov

7

Comme il n'y a pas de réponse marquée comme réponse acceptée, j'ai tenté de créer un exemple de verrouillage en direct;

Le programme original a été écrit par moi en avril 2012 pour apprendre différents concepts du multithreading. Cette fois, je l'ai modifié pour créer un blocage, une condition de course, un blocage de la vie, etc.

Alors, comprenons d'abord l'énoncé du problème;

Problème de cookie Maker

Il existe quelques conteneurs d'ingrédients: ChocoPowederContainer , WheatPowderContainer . CookieMaker prend une certaine quantité de poudre des contenants d'ingrédients pour cuire un cookie . Si un fabricant de cookies trouve un conteneur vide, il recherche un autre conteneur pour gagner du temps. Et attend que Filler remplisse le conteneur requis. Il y a un remplisseur qui vérifie le conteneur à intervalles réguliers et remplit une certaine quantité si un conteneur en a besoin.

Veuillez vérifier le code complet sur github ;

Laissez-moi vous expliquer la mise en œuvre en bref.

  • Je lance Filler en tant que thread démon. Il continuera donc à remplir les conteneurs à intervalles réguliers. Pour remplir un conteneur, il verrouille d'abord le conteneur -> vérifiez s'il a besoin de poudre -> le remplit -> signalez à tous les fabricants qui l'attendent -> déverrouillez le conteneur.
  • Je crée CookieMaker et je définis qu'il peut cuire jusqu'à 8 cookies en parallèle. Et je démarre 8 fils pour cuire des cookies.
  • Chaque thread maker crée 2 sous-thread appelables pour prendre la poudre des conteneurs.
  • sous-thread prend un verrou sur un conteneur et vérifie s'il contient suffisamment de poudre. Sinon, attendez un peu. Une fois que Filler remplit le récipient, il prend la poudre et déverrouille le récipient.
  • Maintenant, il complète d'autres activités comme: faire du mélange et cuire au four, etc.

Jetons un œil dans le code:

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

Tout fonctionne bien jusqu'à ce que Filler remplisse les conteneurs. Mais si j'oublie de démarrer le remplisseur, ou si le remplisseur part en congé inopiné, les sous-threads continuent de changer d'état pour permettre à d'autres fabricants d'aller vérifier le conteneur.

J'ai également créé un démon ThreadTracer qui surveille les états des threads et les blocages. C'est la sortie de la 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]

Vous remarquerez que les sous-threads et changer leurs états et attendre.


4

Un exemple réel (bien que sans code exact) est le verrouillage direct de deux processus concurrents dans le but de corriger un blocage du serveur SQL, chaque processus utilisant le même algorithme d'attente-nouvelle tentative pour réessayer. Bien que ce soit la chance du timing, j'ai vu cela se produire sur des machines séparées avec des caractéristiques de performances similaires en réponse à un message ajouté à un sujet EMS (par exemple, enregistrer une mise à jour d'un seul graphique d'objet plus d'une fois), et ne pas pouvoir contrôler l'ordre de verrouillage.

Une bonne solution dans ce cas serait d'avoir des consommateurs concurrents (éviter les doubles traitements aussi haut que possible dans la chaîne en partitionnant le travail sur des objets indépendants).

Une solution moins souhaitable (ok, dirty-hack) est de briser la malchance de timing (genre de différences de force dans le traitement) à l'avance ou de la briser après une impasse en utilisant différents algorithmes ou un élément aléatoire. Cela pourrait toujours avoir des problèmes car il est possible que l'ordre de prise de verrouillage soit "collant" pour chaque processus, et cela prend un certain temps minimum non pris en compte dans l'attente-nouvelle tentative.

Une autre solution (au moins pour SQL Server) consiste à essayer un niveau d'isolement différent (par exemple, un instantané).


2

J'ai codé l'exemple de 2 personnes passant dans un couloir. Les deux fils s'éviteront dès qu'ils se rendront compte que leurs directions sont les mêmes.

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

Version C # du code de 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 exemple ici pourrait être l'utilisation d'un tryLock chronométré pour obtenir plus d'un verrou et si vous ne pouvez pas tous les obtenir, reculez et réessayez.

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

Je pourrais imaginer qu'un tel code serait problématique car de nombreux threads entrent en collision et attendent d'obtenir un ensemble de verrous. Mais je ne suis pas sûr que ce soit un exemple très convaincant pour moi.


1
pour que ce soit un livelock, vous aurez besoin d'un autre thread pour acquérir ces verrous dans un ordre différent. Si tous les threads utilisent tryLockAll()avec les verrous locksdans le même ordre, il n'y a pas de verrouillage en direct.
JaviMerino

0

Version Python du code de 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()

Bogues: dans use (), print n'est pas utilisé et - plus important encore - l'indicateur faim n'est pas défini sur False.
GDR

0

Je modifie la réponse de @jelbourn. Quand l'un d'eux remarque que l'autre a faim, il (elle) doit relâcher la cuillère et attendre un autre avertissement, pour qu'un vivelock se produise.

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;
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.