C++ 线程lambda中的原子更新
我认为在线程中以这种方式更新原子值是不好的(有时总和看起来不好)C++ 线程lambda中的原子更新,c++,c++11,lambda,thread-safety,C++,C++11,Lambda,Thread Safety,我认为在线程中以这种方式更新原子值是不好的(有时总和看起来不好) std::原子e(0); 自动工作者=[&](大小开始,大小结束,标准::原子和acc){ 双ee=0; 用于(自动k=开始;k!=结束;++k){ ee+=某物[k]; } 附件存储(附件加载()+ee); }; 标准::向量线程(nbThreads); const size\u t grainsize=miniBatchSize/nbThreads; 尺寸=0; for(自动it=std::begin(线程);it!=std:
std::原子e(0);
自动工作者=[&](大小开始,大小结束,标准::原子和acc){
双ee=0;
用于(自动k=开始;k!=结束;++k){
ee+=某物[k];
}
附件存储(附件加载()+ee);
};
标准::向量线程(nbThreads);
const size\u t grainsize=miniBatchSize/nbThreads;
尺寸=0;
for(自动it=std::begin(线程);it!=std::end(线程)-1;++it){
*it=std::thread(worker,work_iter,work_iter+grainsize,std::ref(e));
工作温度+=粒度;
}
threads.back()=std::thread(worker,work_iter,miniBatchSize,std::ref(e));
用于(自动和i:线程){
i、 join();
}
而使用锁防护似乎是可以的
std::atomic<double> e(0);
std::mutex m;
auto worker = [&] (size_t begin, size_t end, std::atomic<double> & acc) {
double ee = 0;
for(auto k = begin; k != end; ++k) {
ee += something[k];
}
{
const std::lock_guard<std::mutex> lock(m);
acc.store( acc.load() + ee );
}
};
std::vector<std::thread> threads(nbThreads);
const size_t grainsize = miniBatchSize / nbThreads;
size_t work_iter = 0;
for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) {
*it = std::thread(worker, work_iter, work_iter + grainsize, std::ref(e));
work_iter += grainsize;
}
threads.back() = std::thread(worker, work_iter, miniBatchSize, std::ref(e));
for(auto&& i : threads) {
i.join();
}
std::原子e(0);
std::互斥m;
自动工作者=[&](大小开始,大小结束,标准::原子和acc){
双ee=0;
用于(自动k=开始;k!=结束;++k){
ee+=某物[k];
}
{
常数标准:锁和防护锁(m);
附件存储(附件加载()+ee);
}
};
标准::向量线程(nbThreads);
const size\u t grainsize=miniBatchSize/nbThreads;
尺寸=0;
for(自动it=std::begin(线程);it!=std::end(线程)-1;++it){
*it=std::thread(worker,work_iter,work_iter+grainsize,std::ref(e));
工作温度+=粒度;
}
threads.back()=std::thread(worker,work_iter,miniBatchSize,std::ref(e));
用于(自动和i:线程){
i、 join();
}
我是对的,我在这里错过了什么?std::ref(e)是问题吗?您希望加载和存储都作为原子操作发生。目前,您的代码执行以下操作:
acc.store(acc.load() + ee);
现在假设一个线程在执行load()
之后立即中断(让我们调用加载的值acc\u old
)。另一个线程执行它的操作(因此修改acc
),然后第一个线程再次运行。它不会重新加载acc
,因为它已经加载了它的值。因此,该线程现在将更新acc
以包含acc\u old+ee
。砰,结果是错误的
而是使用或。两者都保证了整个加法操作的原子行为。即:
acc += ee; // or
acc.fetch_add(ee);
编辑:请注意,这些函数仅适用于从C++20开始的浮点原子。对于整型,从C++11开始就支持整型。因此,如果您需要浮点,您可能必须坚持使用互斥。在这种情况下,我建议将double值和mutex包装在一个类中,这样您就不会意外地错误使用它。问题在于:
acc.store(acc.load()+ee)代码>
加载和存储有两个操作,在它们之间的间隔内,另一个线程可以更改该值
不幸的是,atomic不支持fetch_add
您可以尝试以下方法:
auto atomic_fetch_add = [](std::atomic<double>* obj, double arg)
{
auto expected = obj->load();
while (!atomic_compare_exchange_weak(obj, &expected, expected + arg))
;
return expected;
};
std::atomic<double> e(0);
auto worker = [&] (size_t begin, size_t end, std::atomic<double> & acc) {
double ee = 0;
for(auto k = begin; k != end; ++k) {
ee += something[k];
}
// acc.store( acc.load() + ee );
atomic_fetch_add(&acc, ee);
};
std::vector<std::thread> threads(nbThreads);
const size_t grainsize = miniBatchSize / nbThreads;
size_t work_iter = 0;
for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) {
*it = std::thread(worker, work_iter, work_iter + grainsize, std::ref(e));
work_iter += grainsize;
}
threads.back() = std::thread(worker, work_iter, miniBatchSize, std::ref(e));
for(auto&& i : threads) {
i.join();
}
auto-atomic\u-fetch\u-add=[](标准::原子*obj,双参数)
{
自动预期=obj->load();
而(!原子\u比较\u交换\u弱(obj,&expected,expected+arg))
;
预期收益;
};
std::原子e(0);
自动工作者=[&](大小开始,大小结束,标准::原子和acc){
双ee=0;
用于(自动k=开始;k!=结束;++k){
ee+=某物[k];
}
//附件存储(附件加载()+ee);
原子提取添加(&acc,ee);
};
标准::向量线程(nbThreads);
const size\u t grainsize=miniBatchSize/nbThreads;
尺寸=0;
for(自动it=std::begin(线程);it!=std::end(线程)-1;++it){
*it=std::thread(worker,work_iter,work_iter+grainsize,std::ref(e));
工作温度+=粒度;
}
threads.back()=std::thread(worker,work_iter,miniBatchSize,std::ref(e));
用于(自动和i:线程){
i、 join();
}
虽然不能保证atomic不使用互斥体,但是您必须检查您的实现
auto atomic_fetch_add = [](std::atomic<double>* obj, double arg)
{
auto expected = obj->load();
while (!atomic_compare_exchange_weak(obj, &expected, expected + arg))
;
return expected;
};
std::atomic<double> e(0);
auto worker = [&] (size_t begin, size_t end, std::atomic<double> & acc) {
double ee = 0;
for(auto k = begin; k != end; ++k) {
ee += something[k];
}
// acc.store( acc.load() + ee );
atomic_fetch_add(&acc, ee);
};
std::vector<std::thread> threads(nbThreads);
const size_t grainsize = miniBatchSize / nbThreads;
size_t work_iter = 0;
for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) {
*it = std::thread(worker, work_iter, work_iter + grainsize, std::ref(e));
work_iter += grainsize;
}
threads.back() = std::thread(worker, work_iter, miniBatchSize, std::ref(e));
for(auto&& i : threads) {
i.join();
}