目前正在使用 GCC 查看 C/C++ 中的原子操作,发现内存中自然对齐的全局变量具有原子读取和写入。
然而,我试图按位与一个全局变量,并注意到它归结为一个读取-修改-写入序列,如果有多个线程对该字节值进行操作,那么这会很麻烦。
经过一番研究,我选择了这两个例子:
C 示例- 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;
}
C++ 示例- 使用原子的 C++11fetch_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;
}
其他示例如下,但它们似乎不太直观且计算成本更高。
用一个pthread_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);
使用互斥体lock_guard
#include <mutex>
uint8_t byte;
std::mutex byte_mutex;
void atomic_and() {
std::lock_guard<std::mutex> lock(byte_mutex);
byte &= 0xF0;
}
用一个compare_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));
}
问题
多线程 C/C++ 程序中读取-修改-写入序列的最佳原子方法是什么?
这在 C/C++ 意义上是不正确的,仅在 x86_64 意义上是正确的。确实,x86_64 上的任何对齐加载和存储都是原子的,但这对于抽象机来说是不正确的。同时写入内存的非原子位始终是一场数据竞争,线程清理程序可能会发现错误,即使该体系结构理论上使其安全。
此外,原子地执行的最佳方法
byte &= 0xf0
在 C 和 C++ 中非常相似:其他方法(POSIX 线程、
std::mutex
重compare_exchange
试循环)几乎肯定比函数形式的内置方法更糟糕fetch_and
。如果体系结构不直接提供原子获取与指令,则应选择最佳方式。这不是你需要担心的事情。也可以看看
感谢@PeterCordes 分享这些链接。