c++ - 有没有更好的方法使这段代码线程安全? Thread_local static 似乎是一个生硬的工具

标签 c++ multithreading openmp

下面的代码模拟了一个更大的程序,该程序创建了一个模拟实例,然后使用 firstprivate 将其并行化以使我的实例私有(private)化。但是,该实例本身在其方法中创建了另外两个实例。

结构似乎做作,但我的双手有些束缚:类及其依赖性由我想使用的工具决定,我认为这种情况在科学计算社区中很常见。

它编译得很好,经过手动测试似乎是线程安全的并且可以正常工作。

但我不确定我是否以最佳方式使用 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 关键字,实例只会不断被重新分配,虽然我强烈怀疑它会保持线程私有(private),但它几乎没有用,因为计数器会对于双核机器上的线程 0 和 1,卡在 1 或 2。 (真实世界的应用程序必须能够“计数”并且不受并行线程的干扰。)

我需要什么帮助

现在,我以这样的方式对示例进行建模,即通过相互干扰的计数器会明显违反线程安全,正如我们所看到的那样,当 thread_local 被排除在外但 static 被留下时(两者都没有是愚蠢的). HelpCrunch 类实际上要复杂得多,并且很可能是线程安全的,并且可以在每次循环重复时重新初始化。 (这实际上更好,因为它从它的子实例中获取了一堆变量,这是一个私有(private)实例。)但是你认为我最好也将 thread_local 添加到 solver 的创建中,而不是static 关键字?或者我应该在别处声明实例,在这种情况下我需要帮助传递指针/引用等。

最佳答案

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

其次,注释代码崩溃是因为 GenNo_ptr 具有自动持续时间,因此每次 CrunchManyNos() 完成执行时它都会被销毁。因此,当 first_runfalse 时,您正在取消引用 nullptr 指针。

当涉及到您的具体问题时,使 RanNo staticstatic thread_local 之间存在巨大差异:

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

  • 如果它是 static thread_local(顺便说一句,使用 openmp 你应该更喜欢 threadprivate)它会 在新线程第一次调用 CrunchManyNos() 时创建,并且 将持续线程的持续时间。

关于c++ - 有没有更好的方法使这段代码线程安全? Thread_local static 似乎是一个生硬的工具,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54120259/

相关文章:

multithreading - 生成比线程更多的任务

c - OpenMP 并行化效率不高

c++ - openmp/C++ 简单并行区域返回不一致的结果

c++ - 将 cairo 表面直接渲染到 OpenGL 纹理

ios - GCD 主线程崩溃问题(需要解释)?

java - Java 中基于名称的可重入锁

python - 开始在 python 上录制之前的语音识别流延迟

c++ - 模板化的 Barton 和 Nackman 技巧问题

c++ - 什么是 undefined reference /未解析的外部符号错误,我该如何解决?

c++ - 编译 Love 2D Xcode : Undefined Symbols for Architecture