Ao escrever aplicativos multithread, um dos problemas mais comuns encontrados são os impasses.
Minhas perguntas à comunidade são:
O que é um impasse?
Como você os detecta?
Você lida com eles?
E, finalmente, como você evita que elas ocorram?
Ao escrever aplicativos multithread, um dos problemas mais comuns encontrados são os impasses.
Minhas perguntas à comunidade são:
O que é um impasse?
Como você os detecta?
Você lida com eles?
E, finalmente, como você evita que elas ocorram?
Respostas:
Um bloqueio ocorre quando vários processos tentam acessar o mesmo recurso ao mesmo tempo.
Um processo perde e deve esperar o outro terminar.
Um impasse ocorre quando o processo de espera ainda está retendo outro recurso que o primeiro precisa antes de concluir.
Então, um exemplo:
O recurso A e o recurso B são usados pelo processo X e pelo processo Y
A melhor maneira de evitar conflitos é evitar que os processos se cruzem dessa maneira. Reduza a necessidade de bloquear tudo o que puder.
Nos bancos de dados, evite fazer muitas alterações em tabelas diferentes em uma única transação, evite gatilhos e alterne para leituras otimistas / sujas / nolock o máximo possível.
Deixe-me explicar um exemplo do mundo real (não realmente real) para uma situação de impasse nos filmes de crime. Imagine que um criminoso mantém um refém e, contra isso, um policial também mantém um refém amigo do criminoso. Nesse caso, o criminoso não vai deixar o refém ir se o policial não deixar o amigo deixar ir. Além disso, o policial não vai deixar o amigo do criminoso soltar, a menos que o criminoso libere o refém. Esta é uma situação infindável e sem fim, porque os dois lados estão insistindo no primeiro passo um do outro.
Então, simplesmente, quando dois encadeamentos precisam de dois recursos diferentes e cada um deles possui o bloqueio do recurso que o outro precisa, é um impasse.
Você está namorando com uma garota e, um dia depois de uma discussão, ambos os lados estão com o coração partido e esperando uma ligação que eu sinto muito e perdi . Nessa situação, os dois lados querem se comunicar se, e somente se, um deles recebe uma ligação do outro, desculpe-me . Como nenhum dos dois iniciará a comunicação e a espera em um estado passivo, ambos esperarão que o outro inicie a comunicação que acaba em uma situação de impasse.
Os deadlocks só ocorrerão quando você tiver dois ou mais bloqueios que podem ser adquiridos ao mesmo tempo e eles forem agarrados em ordem diferente.
As formas de evitar conflitos são:
Para definir deadlock, primeiro eu definiria processo.
Processo : Como sabemos, processo não passa de um program
em execução.
Recurso : para executar um processo do programa, são necessários alguns recursos. As categorias de recursos podem incluir memória, impressoras, CPUs, arquivos abertos, unidades de fita, CD-ROMS, etc.
Deadlock : Deadlock é uma situação ou condição em que dois ou mais processos estão mantendo alguns recursos e tentando adquirir mais recursos, e eles não podem liberar os recursos até que concluam a execução.
Condição ou situação de impasse
No diagrama acima, existem dois processos P1 e p2 e existem dois recursos R1 e R2 .
O recurso R1 é alocado para o processo P1 e o recurso R2 é alocado para o processo p2 . Para concluir a execução do processo, o P1 precisa do recurso R2 , portanto, o P1 solicita o R2 , mas o R2 já está alocado para o P2 .
Da mesma forma, o Processo P2 para concluir sua execução precisa de R1 , mas R1 já está alocado para P1 .
ambos os processos não podem liberar seus recursos até e a menos que concluam sua execução. Então, ambos estão esperando por outros recursos e esperarão para sempre. Portanto, esta é uma condição DEADLOCK .
Para que o conflito ocorra, quatro condições devem ser verdadeiras.
e todas essas condições são satisfeitas no diagrama acima.
Um impasse ocorre quando um encadeamento está aguardando algo que nunca ocorre.
Normalmente, isso acontece quando um encadeamento está aguardando um mutex ou semáforo que nunca foi liberado pelo proprietário anterior.
Também acontece frequentemente quando você tem uma situação envolvendo dois threads e dois bloqueios como este:
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
Você geralmente os detecta porque as coisas que você espera que nunca aconteçam, ou o aplicativo trava completamente.
Você pode dar uma olhada nesses artigos maravilhosos , na seção Deadlock . Está em C #, mas a ideia ainda é a mesma para outra plataforma. Cito aqui para facilitar a leitura
Um conflito ocorre quando dois encadeamentos esperam por um recurso mantido pelo outro, portanto nenhum deles pode prosseguir. A maneira mais fácil de ilustrar isso é com dois bloqueios:
object locker1 = new object();
object locker2 = new object();
new Thread (() => {
lock (locker1)
{
Thread.Sleep (1000);
lock (locker2); // Deadlock
}
}).Start();
lock (locker2)
{
Thread.Sleep (1000);
lock (locker1); // Deadlock
}
O impasse é um problema comum nos problemas de multiprocessamento / multiprogramação no SO. Digamos que existem dois processos P1, P2 e dois recursos compartilháveis globalmente R1, R2 e, na seção crítica, ambos os recursos precisam ser acessados
Inicialmente, o sistema operacional atribui R1 ao processo P1 e R2 ao processo P2. Como ambos os processos estão em execução simultaneamente, eles podem começar a executar seu código, mas o PROBLEMA surge quando um processo atinge a seção crítica. Portanto, o processo R1 aguardará o processo P2 liberar R2 e vice-versa ... Então, eles aguardarão para sempre (CONDIÇÃO DE DELOCK).
Uma pequena ANALOGIA ...
Sua mãe (OS),
você (P1),
seu irmão (P2),
maçã (R1),
faca (R2),
seção crítica (cortar maçã com faca).Sua mãe lhe dá a maçã e a faca para seu irmão no começo.
Ambos estão felizes e brincando (Executando seus códigos).
Qualquer um de vocês deseja cortar a maçã (seção crítica) em algum momento.
Você não quer dar a maçã ao seu irmão.
Seu irmão não quer dar a faca para você.
Então vocês dois vão esperar por muito, muito tempo :)
O impasse ocorre quando dois threads adquirem bloqueios que impedem que um deles progrida. A melhor maneira de evitá-los é com um desenvolvimento cuidadoso. Muitos sistemas embarcados se protegem contra eles usando um cronômetro de vigilância (um cronômetro que redefine o sistema sempre que for interrompido por um determinado período de tempo).
Um impasse ocorre quando há uma cadeia circular de encadeamentos ou processos, cada um contendo um recurso bloqueado e tentando bloquear um recurso retido pelo próximo elemento da cadeia. Por exemplo, dois encadeamentos que mantêm respectivamente o bloqueio A e o bloqueio B e estão tentando adquirir o outro bloqueio.
Um programa clássico e muito simples para entender a situação de Deadlock : -
public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println(initialized);
}
}
Quando o thread principal chama Lazy.main, ele verifica se a classe Lazy foi inicializada e começa a inicializar a classe. O encadeamento principal agora define inicializado como false, cria e inicia um encadeamento em segundo plano cujo método de execução define inicializado como true e aguarda a conclusão do encadeamento em segundo plano.
Desta vez, a classe está sendo inicializada por outro thread. Nessas circunstâncias, o thread atual, que é o thread de segundo plano, aguarda o objeto Class até a inicialização ser concluída. Infelizmente, o encadeamento que está iniciando, o encadeamento principal, aguarda a conclusão do encadeamento em segundo plano. Como os dois threads agora estão esperando um pelo outro, o programa está DEADLOCKED.
Um impasse é um estado de um sistema no qual nenhum processo / encadeamento é capaz de executar uma ação. Conforme mencionado por outros, um impasse geralmente é o resultado de uma situação em que cada processo / encadeamento deseja adquirir um bloqueio para um recurso que já está bloqueado por outro (ou mesmo o mesmo) processo / encadeamento.
Existem vários métodos para encontrá-los e evitá-los. Um está pensando muito e / ou tentando muitas coisas. No entanto, lidar com o paralelismo é notoriamente difícil e a maioria (se não todas) as pessoas não serão capazes de evitar completamente os problemas.
Alguns métodos mais formais podem ser úteis se você levar a sério esse tipo de problema. O método mais prático que tenho conhecimento é usar a abordagem teórica do processo. Aqui você modela seu sistema em alguma linguagem de processo (por exemplo, CCS, CSP, ACP, mCRL2, LOTOS) e usa as ferramentas disponíveis para (modelar) verificar se há impasses (e talvez outras propriedades também). Exemplos de conjunto de ferramentas a serem usados: FDR, mCRL2, CADP e Uppaal. Algumas almas corajosas podem até provar seus sistemas livres de impasse usando métodos puramente simbólicos (prova de teoremas; procure Owicki-Gries).
No entanto, esses métodos formais normalmente exigem algum esforço (por exemplo, aprender o básico da teoria do processo). Mas acho que isso é simplesmente uma conseqüência do fato de que esses problemas são difíceis.
O impasse é uma situação que ocorre quando há menos número de recursos disponíveis, conforme solicitado pelo processo diferente. Isso significa que, quando o número de recursos disponíveis se torna menor do que o solicitado pelo usuário, nesse momento o processo entra em condição de espera. Algumas vezes a espera aumenta mais e não há chance de verificar o problema da falta de recursos. essa situação é conhecida como impasse. Na verdade, o deadlock é um grande problema para nós e ocorre apenas no sistema operacional multitarefa. O deadlock não pode ocorrer no sistema operacional de tarefa única porque todos os recursos estão presentes apenas para a tarefa que está sendo executada no momento ......
Acima algumas explicações são legais. Espero que isso também seja útil: https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
Em um banco de dados, quando uma sessão (por exemplo, ora) deseja um recurso mantido por outra sessão (por exemplo, dados), mas essa sessão (dados) também deseja um recurso que é mantido pela primeira sessão (ora). Também pode haver mais de 2 sessões envolvidas, mas a ideia será a mesma. Na verdade, os deadlocks impedem que algumas transações continuem funcionando. Por exemplo: Suponha que ORA-DATA mantenha o bloqueio A e solicite o bloqueio B E o SKU mantenha o bloqueio B e solicite o bloqueio A.
Obrigado,
O impasse ocorre quando um encadeamento aguarda a conclusão de outro encadeamento e vice-versa.
Como evitar?
- Evitar bloqueios aninhados
- Evitar bloqueios desnecessários
- Usar junção de thread ()
Como você o detecta?
execute este comando no cmd:
jcmd $PID Thread.print
referência : geeksforgeeks
Os conflitos não ocorrem apenas com bloqueios, embora essa seja a causa mais frequente. No C ++, você pode criar um deadlock com dois threads e sem bloqueios, basta ter cada chamada de thread join () no objeto std :: thread do outro.
O uso do bloqueio para controlar o acesso a recursos compartilhados é propenso a conflitos, e o planejador de transações sozinho não pode impedir suas ocorrências.
Por exemplo, os sistemas de banco de dados relacional usam vários bloqueios para garantir propriedades ACID da transação .
Não importa qual sistema de banco de dados relacional você esteja usando, os bloqueios sempre serão adquiridos ao modificar (por exemplo, UPDATE
ou DELETE
) um determinado registro da tabela. Sem bloquear uma linha que foi modificada por uma transação em execução no momento, o Atomicity seria comprometido .
Como expliquei neste artigo , um conflito ocorre quando duas transações simultâneas não podem progredir porque cada uma espera a outra liberar uma trava, conforme ilustrado no diagrama a seguir.
Como as duas transações estão na fase de aquisição de bloqueio, nenhuma delas libera um bloqueio antes de adquirir o próximo.
Se você estiver usando um algoritmo de controle de simultaneidade que depende de bloqueios, sempre haverá o risco de executar em uma situação de conflito. Os conflitos podem ocorrer em qualquer ambiente de simultaneidade, não apenas em um sistema de banco de dados.
Por exemplo, um programa de multithreading pode travar se dois ou mais threads estiverem aguardando bloqueios adquiridos anteriormente para que nenhum thread possa progredir. Se isso acontecer em um aplicativo Java, a JVM não poderá forçar apenas um Thread a interromper sua execução e liberar seus bloqueios.
Mesmo que a Thread
classe exponha um stop
método, esse método foi preterido desde o Java 1.1 porque pode fazer com que os objetos sejam deixados em um estado inconsistente após a interrupção de um encadeamento. Em vez disso, Java define um interrupt
método, que age como uma dica, pois um encadeamento interrompido pode simplesmente ignorar a interrupção e continuar sua execução.
Por esse motivo, um aplicativo Java não pode se recuperar de uma situação de deadlock, e é responsabilidade do desenvolvedor do aplicativo ordenar as solicitações de aquisição de bloqueio de forma que os deadlocks nunca ocorram.
No entanto, um sistema de banco de dados não pode impor uma determinada ordem de aquisição de bloqueios, pois é impossível prever quais outros bloqueios uma determinada transação desejará adquirir ainda mais. Preservar a ordem de bloqueio passa a ser responsabilidade da camada de acesso a dados, e o banco de dados só pode ajudar na recuperação de uma situação de conflito.
O mecanismo do banco de dados executa um processo separado que verifica o gráfico de conflito atual em busca de ciclos de espera por bloqueio (causados por conflitos). Quando um ciclo é detectado, o mecanismo de banco de dados escolhe uma transação e a interrompe, fazendo com que seus bloqueios sejam liberados, para que a outra transação possa progredir.
Diferentemente da JVM, uma transação de banco de dados é projetada como uma unidade atômica de trabalho. Portanto, uma reversão deixa o banco de dados em um estado consistente.
Para mais detalhes sobre este tópico, consulte este artigo também.
O Mutex, em essência, é um bloqueio, fornecendo acesso protegido a recursos compartilhados. No Linux, o tipo de dados mutex do encadeamento é pthread_mutex_t. Antes de usar, inicialize-o.
Para acessar recursos compartilhados, você deve bloquear o mutex. Se o mutex já estiver bloqueado, a chamada bloqueará o encadeamento até que o mutex seja desbloqueado. Após a conclusão da visita aos recursos compartilhados, você deverá desbloqueá-los.
No geral, existem alguns princípios básicos não escritos:
Obtenha o bloqueio antes de usar os recursos compartilhados.
Segurando a trava o mais curto possível.
Solte a trava se o encadeamento retornar um erro.