目前正在使用 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++11 fetch_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 上的任何对齐加载和存储都是原子的,但这对于抽象机来说是不正确的。同时写入非原子内存位“总是”是一场数据竞争,线程清理程序可能会发现错误,即使架构理论上使其安全。
此外,最好的方法在 C 和 C++ 中非常相似:
// desired operation
byte &= 0xF0;
// C++, assuming 'byte' is std::atomic_uint8_t
std::uint8_t old = byte.fetch_and(0xf0); /* optionally specify memory order */
// C, assuming 'byte' is atomic_uint8_t
// (from <stdatomic.h>, no compiler extensions needed)
uint8_t old = atomic_fetch_and(&byte, 0xf0); /* optionally atomic_fetch_and_explicit */
其他方法(POSIX 线程、
std::mutex
、
compare_exchange
重试循环)几乎肯定比
fetch_and
函数形式的内置方法更糟糕。