Um módulo de memória (um DIMM) só pode fazer uma coisa de cada vez: uma leitura ou uma gravação. Isso é determinado pelo protocolo no conector de borda do DIMM; uma vez que você envia um comando de leitura ou escrita para um DIMM, ele não pode nem mesmo "ouvir" outro até que o comando anterior esteja completo.
Todos os DIMMs em um barramento de memória comum terão coletivamente essa restrição um por vez. Mas em muitas plataformas de PC existem dois ou três canais de memória , e toda a RAM em um canal pode coletivamente fazer apenas uma coisa de cada vez.
Em máquinas NUMA (aquelas com múltiplos soquetes de CPU e memória "privada" para cada soquete) tudo isso é multiplicado por nCPUs. ou seja, cada CPU pode acessar sua memória local, independentemente do que a outra CPU esteja fazendo com sua memória. No entanto, todos os processadores ainda podem acessar toda a memória RAM, só demora um pouco mais se eles tiverem que passar por outra CPU.
Fundamentalmente, cabe ao código dos aplicativos implementar quaisquer requisitos de serialização que eles tenham para acesso a dados compartilhados. Isso é feito com a ajuda do sistema operacional, que fornecerá vários objetos e funções de sincronização para os aplicativos usarem. Esses objetos têm nomes como semáforos (nome emprestado do sinal da ferrovia), exclusões mútuas (abreviação de "exclusão mútua"), etc. Dependendo do sistema operacional, eles podem implementar um por vez, n-a-vez, gravação exclusiva versus leitura compartilhada, etc., semântica. Essas funções geralmente envolvem "espera" ou "bloqueio": Um thread tenta, por exemplo, adquirir um mutex que, por design, "protege" um recurso compartilhado. Se o mutex já pertence a algum outro segmento, então o segundo (e _n_th) thread solicitante é bloqueado, ou seja,
Cabe aos aplicativos usar essas técnicas quando necessário; o sistema operacional não tem como exigir que, por exemplo, você tenha que possuir um mutex em particular antes de acessar os dados que você pretendia que o mutex protegesse. Esta é uma área de design de programa que pode ser muito difícil para ambos acertarem e terem alto desempenho.
Subsistemas de nível superior, como bancos de dados, têm essa codificação integrada para os dados que gerenciam e apresentam uma interface para o programador que "fala" em termos de elementos do banco de dados em vez de objetos de sincronização: o programador invocaria funções para bloquear uma linha da tabela ou célula individual ou um conjunto de linhas, ou talvez uma tabela inteira, enquanto executa atualizações. Abaixo do mecanismo do banco de dados, serão utilizadas as instalações do sistema operacional, conforme descrito anteriormente.
Internamente, o sistema operacional geralmente implementa essas funções de sincronização com o auxílio de algum tipo de operação de teste e modificação atômica implementada no subsistema de memória. Por "atômica" quero dizer que, uma vez iniciada, a operação é garantida para ser concluída antes que qualquer outra operação atômica possa começar na memória afetada. Um exemplo comumente usado no Windows é a instrução "interlocked compare-and-exchange". x86 / x64 na verdade fornece uma série de instruções, trabalhando em vários tamanhos de dados.
Existem métodos que permitem acesso serializado seguro a dados compartilhados sem usar tais técnicas, mas eles só funcionam para alguns casos especiais (por exemplo, "apenas um leitor e apenas um gravador") ou para determinados tipos de dados.