在多线程编程中, 有两个需要注意的问题, 一个是数据竞争, 另一个是内存执行顺序.
什么是数据竞争(Data Racing)
我们先来看什么是数据竞争(Data Racing), 数据竞争会导致什么问题.
#include <iostream>#include <thread>int counter = 0;void increment() { for (int i = 0; i < 100000; ++i) { // ++counter实际上3条指令 // 1. int tmp = counter; // 2. tmp = tmp + 1; // 3. counter = tmp; ++counter; }}int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Counter = " << counter << "\n"; return 0;}
在这个例子中, 我们有一个全局变量counter, 两个线程同时对它进行增加操作. 理论上, counter的最终值应该是200000. 然而, 由于数据竞争的存在, counter的实际值可能会小于200000.
这是因为, ++counter并不是一个原子操作, CPU会将++counter分成3条指令来执行, 先读取counter的值, 增加它, 然后再将新值写回counter. 示意图如下:
Thread 1 Thread 2--------- ---------int tmp = counter; int tmp = counter;tmp = tmp + 1; tmp = tmp + 1;counter = tmp; counter = tmp;
两个线程可能会读取到相同的counter值, 然后都将它增加1, 然后将新值写回counter. 这样, 两个线程实际上只完成了一次+操作, 这就是数据竞争.
如何解决数据竞争
c++11引入了std::atomic, 将某个变量声明为std::atomic后, 通过std::atomic的相关接口即可实现原子性的读写操作.
现在我们用std::atomic来修改上面的counter例子, 以解决数据竞争的问题.
#include <iostream>#include <thread>#include <atomic>// 只能用bruce-initialization的方式来初始化std::atomic<T>.// std::atomic<int> counter{0} is ok, // std::atomic<int> counter(0) is NOT ok.std::atomic<int> counter{0}; void increment() { for (int i = 0; i < 100000; ++i) { ++counter; }}int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Counter = " << counter << "\n"; return 0;}```
我们将int counter改成了std::atomic<int> counter, 使用std::atomic<int>的++操作符来实现原子性的自增操作.std::atomic提供了以下几个常用接口来实现原子性的读写操作, memory_order用于指定内存顺序, 我们稍后再讲.
`// 原子性的写入值` `std::atomic<T>::store(T val, memory_order sync = memory_order_seq_cst);` `// 原子性的读取值` `std::atomic<T>::load(memory_order sync = memory_order_seq_cst);` `// 原子性的增加` `// counter.fetch_add(1)等价于++counter` `std::atomic<T>::fetch_add(T val, memory_order sync = memory_order_seq_cst);` `// 原子性的减少` `// counter.fetch_sub(1)等价于--counter` `std::atomic<T>::fetch_sub(T val, memory_order sync = memory_order_seq_cst);` `// 原子性的按位与` `// counter.fetch_and(1)等价于counter &= 1` `std::atomic<T>::fetch_and(T val, memory_order sync = memory_order_seq_cst);` `// 原子性的按位或` `// counter.fetch_or(1)等价于counter |= 1` `std::atomic<T>::fetch_or(T val, memory_order sync = memory_order_seq_cst);` `// 原子性的按位异或` `// counter.fetch_xor(1)等价于counter ^= 1` `std::atomic<T>::fetch_xor(T val, memory_order sync = memory_order_seq_cst);`