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();
    }