C++中的std::memory_order提供了多种内存序,通过合理的选择和使用,开发者能够根据多线程应用需求控制操作的顺序和可见性。在多线程应用中,选择合适的内存序既能保证数据的安全性,又能避免不必要的同步开销。本文在简要回顾各类内存序特性的基础上,重点介绍其在实际编程中的选择技巧。
1. 内存序回顾
C++11的std::memory_order包含六种内存序:
2. 使用场景
以下介绍每种内存序的使用场景,并针对部分内存序通过代码示例展示如何在多线程编程中的应用。
2.1 Relaxed
memory_order_relaxed适用于仅保证原子性的非同步——不影响线程间的数据可见性和顺序的场景,适用于无依赖的计数操作等。这也是原子变量最基础的能力。
2.2 Acquire && Release
memory_order_acquire和memory_order_release通常用于跨线程的数据同步,适用于生产者-消费者场景,生产者通过memory_order_release发布数据,消费者通过memory_order_acquire读取,确保在发布数据后其他线程可以读取到正确的数据。用于生产者-消费者、信号标志等数据同步场景,防止指令重排导致的读取数据失效。
示例:生产者-消费者
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> data(0);
std::atomic<bool> ready(false);
void producer() {
data.store(42, std::memory_order_relaxed); // 数据写入
ready.store(true, std::memory_order_release); // 发布数据
}
void consumer() {
while (!ready.load(std::memory_order_acquire)); // 获取数据
std::cout << "Data: " << data.load(std::memory_order_relaxed) << "\n";
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
}2.3 Acquire-Release
memory_order_acq_rel结合了memory_order_acquire和memory_order_release的特性,适用于同时包含读取和写入的复杂同步场景。它确保了读取操作在获取数据后,写入操作对其他线程可见。
2.4 Sequence Consistent
memory_order_seq_cst提供最严格的同步控制,适用于全局顺序一致性需求较高的场景。所有线程观察到的操作顺序一致,适合对数据一致性要求极高的场景,其会产生同步开销,仅必要时使用。
示例:全局顺序一致的计数器
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_seq_cst); // 顺序一致的内存序
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final counter: " << counter.load(std::memory_order_seq_cst) << "\n";
}针对于不同内存序的使用场景,进而归纳出不同场景下内存序的选择技巧如下:
3. 结论
本文回顾了std::memory_order的不同内存序,重点讨论了每种内存序的使用场景,并提出了内存序的选择技巧。因场景不同,开发者应根据实际需求选择合适的内存序,以确保多线程应用的正确性和性能。