原子操作 在古代希腊时期,亚里士多德就曾经发想,如果物体不断分割下去,是否会有无法再继续切割的情况?因此亚里士多德假想的一种粒子,它无法继续切割,这个假想粒子称为原子。由于原子无法分割的特性,所以它只...
原子操作
在古代希腊时期,亚里士多德就曾经发想,如果物体不断分割下去,是否会有无法再继续切割的情况?因此亚里士多德假想的一种粒子,它无法继续切割,这个假想粒子称为原子。由于原子无法分割的特性,所以它只有 "存在" 和 "虚无" 两种状态,这是非常简单与优美的思想,我们希望这种简洁的想法也能在程式语言上表现出来,希望运算只有"完成" 和 "没完成" 两种状态,这种想法称为原子操作。
原子操作的优势
在现代的电脑中,我们对一个整数做加法,如
int a++;
这个加法不是一步完成的,所以在加法的过程中会有一个中间态,假设此时有另一个执行绪读取它,就会有 data race 问题。但如果用一些方法使它变成原子性的,让它只有 "完成" 和 "没完成" ,就可以保证当另一个执行绪读取它时有正确性。
範例
在 C++11 时引入原子操作的类别
#include // atomic
std::atomic a; // 类型为 float
std::atomic b; // 类型为 int
std::atomic<:uint64_t> c; // 类型为 std::uint64_t
没有原子操作
#include
#include
#include
static int cnt = 0;
int main(int argc, char **argv) {
std::vector<:thread> threads;
threads.emplace_back([&](){
for (int i = 0; i < 10000000; ++i) {
cnt++;
}
});
threads.emplace_back([&](){
for (int i = 0; i < 10000000; ++i) {
cnt++;
}
});
for (auto &t : threads) {
t.join();
}
std::cout << " count : " << cnt << std::endl;
return 0;
}
有原子操作
#include
#include
#include
#include
static std::atomic cnt{0};
int main(int argc, char **argv) {
std::vector<:thread> threads;
threads.emplace_back([&](){
for (int i = 0; i < 10000000; ++i) {
cnt++;
}
});
threads.emplace_back([&](){
for (int i = 0; i < 10000000; ++i) {
cnt++;
}
});
for (auto &t : threads) {
t.join();
}
std::cout << " count : " << cnt << std::endl;
return 0;
}
实际运行后可以发现 cnt 不一样。
后记
原子操作有两个常用的函式
std::atomic a;
a.store(100); // 储存 100 到 a
auto b = a.load() // 拿出 a 储存的数字
当然还有一系列强大的函式,透过设计,可以用来取代 mutex,这有机会以后再介绍。