有没有更好的方法可以使此代码具有线程安全性? Thread_local static似乎是一个钝器

时间:2019-01-10 00:06:10

标签: c++ multithreading openmp

以下代码模仿了一个较大的程序,该程序创建一个模拟实例,然后使用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}}关键字?还是应该在其他地方声明该实例,在这种情况下,我将需要有关指针/引用等传递的帮助。

1 个答案:

答案 0 :(得分:0)

首先,您的示例以线程不安全的方式使用全局对象std::cout,并从多个线程中并行访问它。我必须在某些地方添加#pragma omp critical才能获得可读的输出。

第二,注释的代码崩溃,因为GenNo_ptr具有自动持续时间,因此每次CrunchManyNos()完成执行时,该代码便被销毁。因此,当first_runfalse时,您将取消引用nullptr指针。

在谈到您的特定问题时,制作RanNo staticstatic thread_local之间存在巨大差异:

  • 如果为static,则将有一个RanNo实例 第一次执行CrunchManyNos()时初始化。是为了 在某种程度上,全局变量会在 您的示例的并发上下文。

  • 如果它是static thread_local(通过使用openmp,您应该更喜欢threadprivate),它将 在新线程第一次调用CrunchManyNos()时被创建,并且 将在线程持续时间内持续。

相关问题