浅析C++ atomic 和 memory ordering
简介
C++11 中引入了一个新的原子类型 —— std::atomic
,用以在多线程环境中实现原子操作。同时,它也提供了 Memory Ordering 来确保原子操作的顺序性。本文将从理论和实践角度浅析 C++ atomic 和 memory ordering。
原子操作
原子操作是指一个操作要么全部完成,要么全部不完成,没有中间状态。例如对于下面的代码:
int i = 0;
i++;
在单线程环境中,这个代码很简单,i 的值将会被增加 1。但是在多线程环境中,如果两个线程同时执行上述代码中的 i++
操作,由于没有对 i 进行原子化保护,最终 i 的值可能会小于预期的值。
C++11 中提供了 std::atomic
的数据类型,用以实现原子操作。std::atomic
是一种特殊的数据类型,它提供了一些函数来实现原子化操作,确保它们是以原子方式进行的。例如:
#include <atomic>
std::atomic<int> i(0);
i++;
上述代码实现了对 i
的原子增加操作,任意两个线程并发执行该操作也不会导致结果不正确。
除了原子化操作之外,std::atomic
还支持 Load 和 Store 操作。Load 操作从内存中读取数据,而 Store 操作则写入数据。
std::atomic<int> i(0);
int j = i.load();
i.store(1);
在上述代码中,i.load()
会将当前存储在 i 中的值读入 j 中,而 i.store(1)
则会将 1 写入 i 中。
内存顺序
在多线程环境中,原子操作的顺序性是需要被保证的。为了保证原子操作的顺序性,C++11 提供了 Memory Ordering 的机制,即内存顺序。
内存顺序是指代码中各个线程对 Memory Ordering 的要求,以及在应用程序中对信息进行同步的一个机制。
std::memory_order
是一个枚举类型,它提供了多种内存序,如 std::memory_order_relaxed
、std::memory_order_seq_cst
等。
relaxed 内存序
std::memory_order_relaxed
表示没有任何顺序约束,可以使用最松散的约束来实现最高的性能。但是需要注意的是,使用 relaxed 内存序可能导致编写的代码在不同线程中表现出不同的行为。
下面是一个例子:
#include <iostream>
#include <atomic>
#include <thread>
using namespace std;
atomic<int> x(0), y(0);
int r1 = 0, r2 = 0;
void write_x()
{
x.store(1, memory_order_relaxed);
r1 = y.load(memory_order_relaxed);
}
void write_y()
{
y.store(1, memory_order_relaxed);
r2 = x.load(memory_order_relaxed);
}
int main()
{
thread a(write_x);
thread b(write_y);
a.join();
b.join();
cout << "r1: " << r1 << ", r2: " << r2 << endl;
return 0;
}
在该例子中,线程 a 中的 x.store(1, memory_order_relaxed)
和线程 b 中的 y.store(1, memory_order_relaxed)
在执行时都是用 relaxed 内存序进行的。因此,二者之间的顺序在不同的运行环境中是无法保证的,即可能先执行 a,也可能先执行 b,因而 r1 和 r2 的值都有可能输出 0,都有可能输出 1,还可能输出 0 和 1。
release-acquire 内存序
std::memory_order_release
和 std::memory_order_acquire
是针对 release 和 acquire 操作的 Memory Ordering 类型。
release 操作用于将本线程内存中的一个值写回到主存中,并发出信号禁止 CPU 将这些 write 操作放入到 CPU 内存队列中。它与 acquire 操作组合起来,能够实现同步传递的过程。
acquire 操作用于将内存从主存中读入到 CPU 内部缓存中,并等待前面的 release 操作完成后才进行操作。
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> x;
atomic<int> y;
void write_x()
{
x.store(1, std::memory_order_release);
}
void write_y()
{
while (y.load(std::memory_order_acquire) != 1);
cout << "y is 1" << endl;
}
int main()
{
thread a(write_x);
thread b(write_y);
a.join();
b.join();
return 0;
}
在该例子中,线程 a 首先执行了 x.store(1, std::memory_order_release)
来写入 x 的值,该操作使用 release 内存序。线程 b 执行了 y.load(std::memory_order_acquire)
来读取 y 的值,当 y 的值为 1 时,程序输出 "y is 1"。该操作使用 acquire 内存序。
通过 release 和 acquire 操作,线程 b 可以正确地读取到线程 a 中写入的 x 的值,并做出相应的行动。
结论
C++11 提供了原子类型 std::atomic
来确保在多线程环境中的原子操作,并提供了内存顺序机制来保证原子操作的顺序性。在使用时需要根据具体情况进行选择。
参考资料
- C++11 标准 - 29. Lock-free property
- C++11并发编程:原子操作和内存序
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅析C++ atomic 和 memory ordering - Python技术站