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.
concurrency
livelock
Alex Miller
fonte
fonte
Respostas:
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.
fonte
getOwner
método não precisa ser sincronizado também? Do Java efetivo "a sincronização não tem efeito, a menos que leia e grave ".Thread.join()
vez deThread.sleep()
, porque quer esperar que o cônjuge coma?getOwner
método deve ser sincronizado, pois, mesmo quesetOwner
esteja sincronizado, isso não garante que o encadeamento usandogetOwner
(ou acessando o campoowner
diretamente) veja as alterações feitas pelo outro encadeamentosetOwner
. Este vídeo explica isso com muito cuidado: youtube.com/watch?v=WTVooKLLVT8synchronized
palavra-chave comosetOwner
método, porque ler e escrever é uma ação atômica para a variável de referência.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):
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.
fonte
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.
Vamos dar uma olhada no código:
CookieMaker.java
IngredientContainer.java
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;
Você notará esses sub-threads e alterará seus estados e espera.
fonte
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).
fonte
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.
fonte
Versão C # do código de jelbourn:
fonte
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.
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.
fonte
tryLockAll()
com os bloqueios nalocks
mesma ordem, não haverá livelock.Versão em Python do código de jelbourn:
fonte
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.
fonte
fonte