std::atomic_ref
{}std::atomic_ref
类型对其引用的对象进行原子操作。
使用std::atomic_ref
进行多线程读写时不会造成数据争用。被引用对象的生命周期必须超过std::atomic_ref
。操作std::atomic_ref
的子对象是未定义行为。
你可能认为在一个原子内使用引用可以实现这种操作,实际上不可以:
#include
#include
#include
#include
#include struct ExpensiveToCopy
{int counter{};
};int getRandom(int begin, int end)
{std::random_device seed; std::mt19937 engine(seed()); std::uniform_int_distribution<> uniformDist(begin, end);return uniformDist(engine);
}void count(ExpensiveToCopy& exp)
{std::vector v;std::atomic counter{exp.counter}; for (int n = 0; n < 10; ++n){v.emplace_back([&counter]{auto randomNumber = getRandom(100, 200); for (int i = 0; i < randomNumber; ++i) { ++counter; }});}for (auto& t : v) t.join();
}int main()
{std::cout << std::endl;ExpensiveToCopy exp; count(exp);std::cout << "exp.counter: " << exp.counter << '\n';std::cout << std::endl;
}
最后的结果应该接近1500,所以出现了错误,原因是 std::atomic
实际上创建了一个副本。
接下来使用一个简单的示例演示一下:
int main(int argc, char* argv[])
{int val{ 10086 };int& ref = val;std::atomic atomicRef{ ref };++atomicRef;std::cout << std::format("val = {}, ref = {}, atomicRef = {}", val, ref, atomicRef.load()) << std::endl;
}
使用std::atomic_ref
代替std::atomic
解决这个问题
#include
#include
#include
#include
#include struct ExpensiveToCopy
{int counter{};
};int getRandom(int begin, int end)
{std::random_device seed;std::mt19937 engine(seed());std::uniform_int_distribution<> uniformDist(begin, end);return uniformDist(engine);
}void count(ExpensiveToCopy& exp)
{std::vector v;std::atomic_ref counter{exp.counter};for (int n = 0; n < 10; ++n){v.emplace_back([&counter]{auto randomNumber = getRandom(100, 200);for (int i = 0; i < randomNumber; ++i) { ++counter; }});}for (auto& t : v) t.join();
}int main()
{std::cout << std::endl;ExpensiveToCopy exp;count(exp);std::cout << "exp.counter: " << exp.counter << '\n';std::cout << std::endl;
}
看到这里你可能会想为什么不把计数器最开始就定义成原子变量呢像这样:
struct ExpensiveToCopy {
std::atomic counter{};
};
这确实是一个有效的方法,但是对原子变量的操作时同步的,使用std::atomic_ref
可以让你显式的控制何时需要对原子变量进行原子访问,因为大多数时间里你可能只需要读取这个变量。
下一篇:后端人眼中的Vue(四)