下面有一段代码:
#include <iostream>
#include <fstream>
#include <limits>
#include <chrono>
#include <omp.h>
const long int N=1000000000L;
class Counter {
public:
Counter():n(0),N(0){}
void operator()()
{
if(n==INT_MAX)
{
#pragma omp atomic update
++N;
#pragma omp atomic write
n=1;
}
else
{
#pragma omp atomic update
++n;
}
}
long int GetTotalCount()
{
return (static_cast<long int>(n)+static_cast<long int>(N)*static_cast<long int>(INT_MAX));
}
friend std::ostream & operator<<(std::ostream & o, Counter & counter)
{
o<<counter.GetTotalCount()<<" ("<<counter.N<<","<<counter.n<<") = "
<<static_cast<long int>(counter.N)*static_cast<long int>(counter.INT_MAX)+static_cast<long int>
(counter.n)
<<std::endl;
return o;
}
private:
const int INT_MAX=std::numeric_limits<int>::max();
int n;
int N;
};
int main(int argc, char **argv)
{
const auto begin = std::chrono::steady_clock::now();
Counter counter;
#pragma omp parallel for simd
for(long int i=0; i<N; ++i)
{
if(i%2==0) counter();
}
const auto end = std::chrono::steady_clock::now();
std::cout<<"N="<<N<<std::endl;
std::cout<<"Total count="<<counter;
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end-begin).count();
std::cout<<"t="<<time<<" ms"<<std::endl;
return 0;
}
它在所有N<=1000000000的情况下都能正常工作,并给出以下输出:
n=1000000000
总数=500000000(0,500000000)=500000000
t=11297毫秒
但如果要使N大10倍,则输出是不正确的(第2行):
n=10000000000
总数=705032704(0,705 032704)=705032704
t=112660毫秒
这里的第二行必须是
总数=500000000(2,500000000)=705032706
我不能理解为什么程序在N=1e10时工作不正确,尽管N是长int。
检查中存在竞态条件:
if(n==INT_MAX)
{
// Nothing prevents another thread to read n here and enter the same branch
#pragma omp atomic update
++N;
#pragma omp atomic write
n=1;
}
因此,两个线程可能同时决定重置n
和递增n
。
除此之外,您还必须为if检查本身逐个读取n
,尽管这仅仅是不够的。
总体而言,最好使用实际的OpenMP约简或自定义构建的约简,以及支持实际原子操作的足够大的数据类型。