当使用C++中的原子类型进行编程时,需要指定原子操作的内存顺序(Memory Order),以保证多线程下的正确性和一致性。
C++中原子操作的内存顺序一共有4种:
- memory_order_relaxed:最轻松的内存顺序,不会保证原子操作的顺序,也不保证操作的内存可见性。当我们要进行仅仅是读写共享内存而无需考虑同步问题的操作时,可以使用memory_order_relaxed。
示例1:使用relaxed内存顺序进行无锁统计
#include <iostream>
#include <thread>
#include <atomic>
int main() {
std::atomic<int> counter(0);
std::thread thread1([&counter]() {
for (int i = 0; i < 100000; i++) {
counter.fetch_add(1, std::memory_order_relaxed);
}
});
std::thread thread2([&counter]() {
for (int i = 0; i < 100000; i++) {
counter.fetch_add(1, std::memory_order_relaxed);
}
});
thread1.join();
thread2.join();
std::cout << counter << std::endl;
return 0;
}
在这个示例中,我们使用了memory_order_relaxed内存顺序进行了两次原子操作,每次都是将counter原子变量加1。由于C++标准未规定内存操作的执行顺序以及内存可见性,所以在不同的平台和不同的编译器下,结果可能不同。但是在本示例中,多线程下的结果符合预期。
- memory_order_acquire:当一个线程使用memory_order_acquire方式读取一个原子变量时,它自己的所有后续操作都不能在此之前执行,以此保证前面的读取的值是最新的。
示例2:使用acquire-read和release-write解决多线程下的生产消费模型
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
using namespace std;
vector<int> pool;
atomic<int> pool_index(0);
void producer() {
for (int i = 0; i < 100000; i++) {
int index = pool_index.fetch_add(1, std::memory_order_relaxed);
pool[index] = i;
// 发布对第index个元素的写操作
atomic_thread_fence(std::memory_order_release);
}
}
void consumer() {
while (true) {
int index = pool_index.fetch_sub(1, std::memory_order_acquire);
// 加载第index个元素的值
atomic_thread_fence(std::memory_order_acquire);
if (index <= 0) {
// 如果生产者已经停止了,则退出消费线程
break;
}
cout << "consume: " << pool[index - 1] << endl;
}
}
int main() {
pool.resize(100000);
thread t1(producer);
thread t2(consumer);
thread t3(consumer);
t1.join();
pool_index.store(0); // 停止生产者
t2.join();
t3.join();
return 0;
}
在本示例中,我们通过使用acquire读和release写操作,避免了内存可见性和顺序的问题,从而实现了多个生产者和多个消费者协同工作的生产消费模型。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解c++ atomic原子编程中的Memory Order - Python技术站