Quando dois processadores tentam acessar o mesmo local na memória global no mesmo instante, uma condição de corrida
ocorre. O resultado depende inteiramente da arquitetura de memória do sistema e de como ele lida com o acesso simultâneo. Existem várias possibilidades:
*
Comportamento indefinido: Este é o pior cenário. O resultado do acesso à memória é imprevisível. A gravação de um processador pode substituir os outros, ou partes de ambos podem ser intercalados, levando a dados corrompidos. Não há garantia de qual operação do processador terá sucesso ou como os dados serão afetados. Isso é comum em sistemas sem mecanismos de sincronização de acesso à memória.
*
Corrupção de dados: A gravação de um processador pode substituir os dados escritos pelo outro processador, resultando em dados perdidos ou valores incorretos. Este é um resultado muito comum se não houver sincronização.
*
Resultado arbitrário: O hardware ou o sistema operacional do sistema pode escolher o acesso de um processador ao sucesso e o outro para falhar, ou pode combinar as operações de uma maneira inesperada. O resultado não é determinístico.
*
Arbitragem no nível de hardware: Algumas arquiteturas podem ter mecanismos de hardware (como um árbitro de ônibus) que priorizam um processador em relação ao outro. Isso introduz um elemento não determinístico, pois a prioridade pode variar dependendo de vários fatores.
*
Exceção/erro: O sistema pode detectar o conflito e levantar uma exceção ou erro, potencialmente interrompendo a execução ou fazendo com que o programa trave. No entanto, isso não é garantido; Muitos sistemas simplesmente permitem que a condição de corrida prossiga sem notificação.
Para evitar esses problemas, os programadores devem usar mecanismos de sincronização. Esses mecanismos aplicam a ordem dos acessos à memória, impedindo as condições de raça. Exemplos incluem:
*
Mutexes (exclusão mútua): Apenas um processador pode manter o mutex a qualquer momento, impedindo o acesso simultâneo a recursos compartilhados.
*
semáforos: Mais geral que os mutexes, permitindo um controle mais complexo do acesso a recursos compartilhados.
*
operações atômicas: Operações que garantem a serem executadas atomicamente (como uma unidade única e indivisível), impedindo a modificação simultânea.
*
barreiras de memória/cercas: Eles aplicam a ordem das operações de memória, garantindo que certas operações sejam concluídas antes do início de outras pessoas.
Em suma, o acesso simultâneo ao mesmo local de memória sem sincronização adequada é um sério erro de programação que pode levar a um comportamento imprevisível e não confiável. A programação robusta multiprocessadora requer consideração e implementação cuidadosas das técnicas de sincronização.