Atualmente olhando para operações atômicas em C/C++ usando GCC e descobri que variáveis globais alinhadas naturalmente na memória têm leituras e gravações atômicas.
No entanto, eu estava tentando bitwise AND uma variável global e percebi que se resume a uma sequência de leitura-modificação-gravação que é problemática se houver vários threads operando naquele valor de byte.
Depois de algumas pesquisas, decidi por esses dois exemplos:
Exemplo C - extensão GCC__sync_fetch_and_and
#include <stdio.h>
#include <stdint.h>
uint8_t byteC = 0xFF;
int main() {
__sync_fetch_and_and(&byteC, 0xF0);
printf("Value of byteC: 0x%X\n", byteC);
return 0;
}
Exemplo C++ - C++11 usando atômicofetch_and
#include <iostream>
#include <atomic>
std::atomic<uint8_t> byteCpp(0xFF);
int main() {
byteCpp.fetch_and(0xF0);
std::cout << "Value of byteCpp: 0x" << std::hex << static_cast<int>(byteCpp.load()) << std::endl;
return 0;
}
Seguem outros exemplos, mas parecem menos intuitivos e mais caros computacionalmente.
Usando umpthread_mutex_lock
uint8_t byte = 0xFF;
pthread_mutex_t byte_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&byte_mutex);
byte &= 0xF0;
pthread_mutex_unlock(&byte_mutex);
Usando um mutexlock_guard
#include <mutex>
uint8_t byte;
std::mutex byte_mutex;
void atomic_and() {
std::lock_guard<std::mutex> lock(byte_mutex);
byte &= 0xF0;
}
Usando umcompare_exchange_weak
std::atomic<uint8_t> byte;
void atomic_and() {
uint8_t old_val, new_val;
do {
old_val = byte.load();
new_val = old_val & 0xF0;
} while (!byte.compare_exchange_weak(old_val, new_val));
}
Pergunta
Qual é o melhor método atômico para uma sequência de leitura-modificação-gravação em um programa C/C++ multithread?
Isso não é correto no sentido C/C++, apenas no sentido x86_64. É verdade que quaisquer cargas e armazenamentos alinhados em x86_64 são atômicos, mas isso não é correto para a máquina abstrata. Gravar em um bit não atômico de memória simultaneamente é sempre uma corrida de dados, e os desinfetantes de encadeamento podem detectar o erro, mesmo que a arquitetura teoricamente o torne seguro.
Além disso, a melhor maneira de fazer
byte &= 0xf0
atomicamente é muito semelhante em C e C++:Os outros métodos (encadeamentos POSIX,
std::mutex
loopcompare_exchange
de repetição) são quase certamente piores do que o modo integrado na forma defetch_and
funções. Se a arquitetura não fornecer diretamente uma instrução busca-AND atômica, a melhor maneira deve ser escolhida. Não é algo com que você tenha que se preocupar.Veja também
Obrigado a @PeterCordes por compartilhar esses links.