Bom exemplo de livelock?


141

Eu entendo o que é livelock, mas eu queria saber se alguém tinha um bom exemplo disso baseado em código? E por código, não quero dizer "duas pessoas tentando passar uma da outra em um corredor". Se eu ler isso de novo, vou perder meu almoço.


96
Que tal uma simulação de software de duas pessoas tentando se superar em um corredor?
1800 INFORMAÇÃO

36
Maldito seja! Perdi meu almoço!
Alex Miller

3
Estranhamente apropriado: seuss.wikia.com/wiki/The_Zax
NotMe

Piada relacionada a sujeitos
Jorjon

4
Duas pessoas tentando passar por um corredor: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Respostas:


119

Aqui está um exemplo muito simples de livelock em Java, onde marido e mulher estão tentando comer sopa, mas só têm uma colher entre eles. Cada cônjuge é educado demais e passará a colher se o outro ainda não tiver comido.

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
O getOwnermétodo não precisa ser sincronizado também? Do Java efetivo "a sincronização não tem efeito, a menos que leia e grave ".
Sanghyun Lee

Ele não deveria usar, em Thread.join()vez de Thread.sleep(), porque quer esperar que o cônjuge coma?
Solace

o que devemos fazer para superar o problema do livelock neste exemplo em particular?
Thor

1
O getOwnermétodo deve ser sincronizado, pois, mesmo que setOwneresteja sincronizado, isso não garante que o encadeamento usando getOwner(ou acessando o campo ownerdiretamente) veja as alterações feitas pelo outro encadeamento setOwner. Este vídeo explica isso com muito cuidado: youtube.com/watch?v=WTVooKLLVT8
Timofey

2
Você não precisa usar a synchronized palavra-chave como setOwnermétodo, porque ler e escrever é uma ação atômica para a variável de referência.
atiqkhaled

75

Comentários irreverentes à parte, um exemplo que se sabe surgir está no código que tenta detectar e lidar com situações de conflito. Se dois encadeamentos detectam um impasse e tentam se afastar um do outro, sem cuidado, eles acabam presos em um loop sempre "se afastando" e nunca conseguindo avançar.

Por "afastar-se", quero dizer que eles soltariam a fechadura e tentariam deixar que o outro a adquirisse. Podemos imaginar a situação com dois threads fazendo isso (pseudocódigo):

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

Condições de corrida à parte, o que temos aqui é uma situação em que os dois threads, se entrarem ao mesmo tempo, acabam rodando no loop interno sem prosseguir. Obviamente, este é um exemplo simplificado. Uma solução ingênua seria colocar algum tipo de aleatoriedade na quantidade de tempo que os threads esperariam.

A solução correta é sempre respeitar a hierarquia de bloqueio . Escolha uma ordem na qual você adquire os bloqueios e atenha-se a isso. Por exemplo, se os dois encadeamentos sempre adquirirem lock1 antes de lock2, não haverá possibilidade de conflito.


Sim, eu entendo isso. Estou procurando um exemplo de código real disso. A questão é o que significa "deixar de lado" e como ele produz esse cenário.
Alex Miller

Entendo que este é um exemplo artificial, mas é provável que isso possa levar a um livelock? Não seria muito mais provável que, eventualmente, uma janela se abrisse onde uma função pudesse pegar as duas, devido a inconsistências no momento em que os encadeamentos são executados em voz alta e quando são agendados.
precisa saber é o seguinte

Embora não seja um livelock estável, porque eles vão, obviamente, sair dela, eventualmente, eu acho que se encaixa na descrição bem o suficiente
1800 INFORMATION

Exemplo excelente e significativo.
Alecov 13/05

7

Como não há resposta marcada como resposta aceita, tentei criar um exemplo de bloqueio ativo;

O programa original foi escrito por mim em abril de 2012 para aprender vários conceitos de multithreading. Desta vez, modifiquei-o para criar impasse, condição de corrida, livelock etc.

Então, vamos entender primeiro a declaração do problema;

Cookie Maker Problem

Existem alguns recipientes de ingredientes: ChocoPowederContainer , WheatPowderContainer . O CookieMaker leva uma certa quantidade de pó dos recipientes dos ingredientes para assar um biscoito . Se um fabricante de cookies encontrar um contêiner vazio, ele procurará outro contêiner para economizar tempo. E aguarda até o Filler preencher o recipiente necessário. Existe um Enchedor que verifica o contêiner em intervalos regulares e preenche alguma quantidade se um contêiner precisar dele.

Por favor, verifique o código completo no github ;

Deixe-me explicar sua implementação em breve.

  • Eu começo Filler como fio daemon. Portanto, ele continuará enchendo recipientes em intervalos regulares. Para encher um recipiente primeiro, ele trava o recipiente -> verifique se precisa de pó -> enche -> sinaliza para todos os fabricantes que estão esperando por ele -> destranca o recipiente.
  • Crio o CookieMaker e defino que ele pode assar até 8 cookies em paralelo. E inicio 8 threads para fazer biscoitos.
  • Cada thread do criador cria 2 sub-threads solicitáveis ​​para tirar o pó dos contêineres.
  • o sub-fio pega uma trava em um recipiente e verifica se há pó suficiente. Caso contrário, aguarde um pouco. Depois de preencher o recipiente, ele pega o pó e desbloqueia o recipiente.
  • Agora ele completa outras atividades, como: fazer mistura e assar etc.

Vamos dar uma olhada no código:

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

Tudo corre bem até o Filler encher os recipientes. Mas se eu esquecer de iniciar o preenchimento ou o preenchimento for inesperado, os sub-threads continuarão alterando seus estados para permitir que outro fabricante vá e verifique o contêiner.

Eu também criei um daemon ThreadTracer que vigia os estados e os deadlocks dos threads. Essa é a saída do 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]

Você notará esses sub-threads e alterará seus estados e espera.


4

Um exemplo real (embora sem código exato) é o bloqueio ativo de dois processos concorrentes na tentativa de corrigir um impasse no servidor SQL, com cada processo usando o mesmo algoritmo de espera e nova tentativa para tentar novamente. Embora seja a sorte do tempo, eu já vi isso acontecer em máquinas separadas com características de desempenho semelhantes em resposta a uma mensagem adicionada a um tópico do EMS (por exemplo, salvar uma atualização de um gráfico de objeto único mais de uma vez) e não poder controlar a ordem de bloqueio.

Uma boa solução nesse caso seria ter consumidores concorrentes (impedir o processamento duplicado o mais alto possível da cadeia, particionando o trabalho em objetos não relacionados).

Uma solução menos desejável (ok, hack sujo) é interromper o azar de tempo (tipo de diferença de força no processamento) antecipadamente ou quebrá-lo após um impasse usando algoritmos diferentes ou algum elemento aleatório. Isso ainda pode ter problemas, porque é possível que a ordem de bloqueio seja "pegajosa" para cada processo, e isso leva um certo tempo mínimo não contabilizado na espera de nova tentativa.

Outra solução (pelo menos para o SQL Server) é tentar um nível de isolamento diferente (por exemplo, instantâneo).


2

Eu codifiquei o exemplo de duas pessoas passando em um corredor. Os dois threads evitarão um ao outro assim que perceberem que suas direções são as mesmas.

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

Versão C # do código 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

Um exemplo aqui pode estar usando um tryLock cronometrado para obter mais de um bloqueio e, se você não puder obtê-los, recue e tente novamente.

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

Eu poderia imaginar que esse código seria problemático, pois você tem muitos threads colidindo e aguardando para obter um conjunto de bloqueios. Mas não tenho certeza se isso é muito atraente para mim como um exemplo simples.


1
para que isso seja um livelock, você precisará de outro thread para adquirir esses bloqueios em uma ordem diferente. Se todos os threads forem usados tryLockAll()com os bloqueios na locksmesma ordem, não haverá livelock.
18714 JaviMerino

0

Versão em Python do código 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()

Erros: em use (), print não é usado e - mais importante - o sinalizador de fome não está definido como False.
GDR

0

Modifico a resposta de @jelbourn. Quando um deles percebe que o outro está com fome, ele (ela) deve soltar a colher e esperar outra notificação, para que aconteça um 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;
        }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.