以下代码模仿了一个较大的程序,该程序创建一个模拟实例,然后使用firstprivate
将其并行化以使我的实例私有。但是,实例本身在其方法内又创建了两个实例。
结构似乎是人为的,但我的双手有些束缚:类和它们的依赖性由我想使用的工具决定给我,我认为这种情况在科学计算社区中很常见。
它可以很好地编译,并且经过手动测试似乎是线程安全的并且可以正常工作。
但是我不确定我是否以最佳方式使用C ++技术,因为我怀疑我可以在内存层次结构中进一步声明实例,并避免自己可能必须通过传递实例来使用static
变量通过引用我的其他实例或类似的东西在parallel
区域中创建。我怀疑这是最好的,因为#pragma omp parallel {}
括号内的所有内容都在线程本地。
因此,我的目标是为每个类创建两个(或多个)线程局部的独立实例,尤其是GenNo,因为它模拟了一个随机数生成器,该生成器将在每个线程中播种一次,然后用相同的种子,尽管在这里我以一种可预测的方式更改了我所说的“种子”,只是为了了解程序的行为并揭示违反线程安全/竞争条件的行为。
注释掉的代码不起作用,但是在程序以SIGSEGV 11.
退出时产生了“段错误”,我相信并行部署时“唯一”指针不是那么独特。总体而言,此解决方案似乎更为优雅,我希望使其能够正常工作,但很高兴听到您的评论。
为了获得std::unique_ptr
功能,必须将以thread_local static
开头的行注释掉,并删除其他注释。
#include <iostream>
#include <omp.h>
//#include <memory>
class GenNo
{
public:
int num;
explicit GenNo(int num_)
{
num = num_;
};
void create(int incr)
{
num += incr;
}
};
class HelpCrunch{
public:
HelpCrunch() {
}
void helper(int number)
{
std::cout << "Seed is " << number << " for thread number: " << omp_get_thread_num() << std::endl;
}
};
class calculate : public HelpCrunch
{
public:
int specific_seed;
bool first_run;
void CrunchManyNos()
{
HelpCrunch solver;
thread_local static GenNo RanNo(specific_seed);
//std::unique_ptr<GenNo> GenNo_ptr(nullptr);
/*
if(first_run == true)
{
GenNo_ptr.reset(new GenNo(specific_seed));
first_run = false;
}
solver.helper(GenNo_ptr->num);
*/
RanNo.create(1);
solver.helper(RanNo.num);
//do actual things that I hope are useful.
};
};
int main()
{
calculate MyLargeProb;
MyLargeProb.first_run = true;
#pragma omp parallel firstprivate(MyLargeProb)
{
int thread_specific_seed = omp_get_thread_num();
MyLargeProb.specific_seed = thread_specific_seed;
#pragma omp for
for(int i = 0; i < 10; i++)
{
MyLargeProb.CrunchManyNos();
std::cout << "Current iteration is " << i << std::endl;
}
}
return 0;
}
现在,带有thread_local static
关键字的输出为:
Seed is 2 for thread number: 1
Current iteration is 5
Seed is 3 for thread number: 1
Current iteration is 6
Seed is 4 for thread number: 1
Current iteration is 7
Seed is 5 for thread number: 1
Current iteration is 8
Seed is 6 for thread number: 1
Current iteration is 9
Seed is 1 for thread number: 0
Current iteration is 0
Seed is 2 for thread number: 0
Current iteration is 1
Seed is 3 for thread number: 0
Current iteration is 2
Seed is 4 for thread number: 0
Current iteration is 3
Seed is 5 for thread number: 0
Current iteration is 4
在不使用thread_local
但保留static
的情况下,我得到了:
Seed is 2 for thread number: 1
Current iteration is 5
Seed is 3 for thread number: 1
Current iteration is 6
Seed is 4 for thread number: 1
Current iteration is 7
Seed is 6 for thread number: 1
Current iteration is 8
Seed is 7 for thread number: 1
Current iteration is 9
Seed is 5 for thread number: 0
Current iteration is 0
Seed is 8 for thread number: 0
Current iteration is 1
Seed is 9 for thread number: 0
Current iteration is 2
Seed is 10 for thread number: 0
Current iteration is 3
Seed is 11 for thread number: 0
Current iteration is 4
如果我完全忽略了static
关键字,则该实例将一直被重新分配,尽管我强烈怀疑它对线程是私有的,但它的用处很小,因为计数器会停留在该位置双核计算机上线程0和1的1或2。 (现实世界中的应用程序必须能够“计数”并且不受并行线程的干扰。)
现在,我以这样的方式对示例进行建模:通过计数器相互干扰,会明显违反线程安全性,就像我们看到的那样,当thread_local被忽略而静态被保留时(两者都不愚蠢) )。实际上,类HelpCrunch
更为复杂,并且很可能在每次循环重复时都可以重新初始化,并且很安全。 (这实际上更好,因为它从作为私有实例的子代中拾取了一堆变量。)但是,您是否认为最好在不创建{{1}的情况下也将thread_local添加到solver
中呢? 1}}关键字?还是应该在其他地方声明该实例,在这种情况下,我将需要有关指针/引用等传递的帮助。
答案 0 :(得分:0)
首先,您的示例以线程不安全的方式使用全局对象std::cout
,并从多个线程中并行访问它。我必须在某些地方添加#pragma omp critical
才能获得可读的输出。
第二,注释的代码崩溃,因为GenNo_ptr
具有自动持续时间,因此每次CrunchManyNos()
完成执行时,该代码便被销毁。因此,当first_run
为false
时,您将取消引用nullptr
指针。
在谈到您的特定问题时,制作RanNo
static
或static thread_local
之间存在巨大差异:
如果为static
,则将有一个RanNo
实例
第一次执行CrunchManyNos()
时初始化。是为了
在某种程度上,全局变量会在
您的示例的并发上下文。
如果它是static thread_local
(通过使用openmp,您应该更喜欢threadprivate
),它将
在新线程第一次调用CrunchManyNos()
时被创建,并且
将在线程持续时间内持续。