在不同线程之间通过(c++) OpenMP共享信息

3

我对并行编程比较新,想要在c++中通过openmp完成以下任务。

我有一些(假设为4个)相对复杂的对象/计算。它们都是类似的,但每个都太复杂了,无法并行化(因此它们运行串行)。所以我的想法是为每个对象/计算使用不同的线程/cpu,也就是说,我想将计算分散到我的核心上。虽然这可能不是在这种情况下最有效的并行使用方法,但由于每个计算的复杂性很高,这可能是最容易实现的方法。

虽然这种方法能够工作,

#pragma omp parallel
{
    #pragma omp for
    for(int i = 0; i < 4; i++)
    {
       obj[i].calculate();    
    }
}

我希望在这些对象之间进一步交换信息,例如一个整数(或更复杂的对象)“a”应该在每次计算期间进行修改(虽然我无法预测何时以及多频繁,但通常情况下会发生多次)。如果它被修改,那么信息也需要被纳入到每个其他计算中。虽然具体的信息交换(再次)相对复杂,但这也是“calculate”方法(隐式地)完成的。总的来说,这应该像上面那样,加上额外的整数“a”,所有计算方法都会读写它:

int a;
#pragma omp parallel
{
   #pragma omp for
   for(int i = 0; i < 4; i++)
   {
       obj[i].calculate();    
   }
}

所以我的问题是,我如何防止在“a”上出现“数据竞争”。这意味着我如何生成一个对象,每次只能被一个线程访问,而不需要在“计算”方法本身中详细说明?OpenMP是否提供此功能,如果没有,哪个库可以提供此功能?
最好的问候和先感谢!

锁定整个计算过程中所需的 'a' 相当简单,但这样会有效地串行化您的计算,是吗?所有的计算都能够在另一个线程异步修改可能复杂的'a'的情况下生存下来吗?(我怀疑不行!)如果不能,那么有没有可能确定每个计算中安全接受新的 'a' 的点?如果在某个中间点接受新的 'a',是否会使该次计算结果无效? - Martin James
我不确定我是否理解了你的评论。计算可能会是“长时间”的(长达数天),"a"有额外的字符,了解“更新”的“a”可能会节省大量时间,但这并不是必要的功能。因此,对于新的“a”进行检查会经常被重复执行,但大多数情况下都是负面的(没有新的“a”)。所以一个“旧”的“a”并没有害处,但一个错误(异步?)则会有害。在我看来,问题在于从一个线程修改“a”,而从另一个线程读取“a”。 - Martin
我的问题在于理解如何在计算的某个中间和未知阶段更改参数(即使以“原子”方式进行所有'a'的更改),可能会导致任何好的结果-一些计算将使用旧的'a',而另一些则使用新的'a'-对我来说,这是一件坏事。然而,您的回复表明了重要的一点-计算在适当的时候要求一个新的'a'(尽管如果没有可用的新'a',请求可能会被拒绝)。这使得事情变得容易得多。 - Martin James
如果通过指针访问'a',那么您是否可以在某个对所有线程可访问的变量中更改指针?不要释放/处理任何过时的'a',因为一些线程将在一段时间内使用旧的'a' - 只需将它们保留到运行结束即可。 - Martin James
2个回答

1
根据你的描述,当每个线程都需要等待更新的信息时,我不确定并行执行是否能对你有所帮助。
无论如何,你可以通过使用flush、atomic和critical指令来更新变量,避免竞争条件。最佳选择严重依赖于哪些线程需要更新a或获取已更新的a。
critical:所有线程按照顺序执行代码,每次只有一个线程执行。
atomic:内存受到保护,防止多次写入,并在内部被替换为critical。
flush:更新共享变量,并在critical中隐式调用。
最后,barrier确保所有线程都达到了代码中相同的点。
引用: 我想在这些对象之间进一步交换信息,例如,在每次计算期间修改一个整数(或更复杂的对象)“a”(虽然我无法预测何时以及多频繁地进行修改,但通常会进行多次修改)。

这个语句有点恼人,因为你应该知道什么时候需要更新你的 a。当你这样做的时候,你需要在所有线程中设置一个屏障,在关键段中更新 a 并继续并行执行。那么有多少个线程会更新 a 呢?是主线程还是所有线程都会更新?

如果只有一个线程需要更新 a,那么另一个选项是 single 指令。它的代码仅由一个线程执行,并在执行后具有隐式屏障和隐式刷新。这些是适当更新你复杂对象 a 到所有线程的通用选项。祝你好运。


从目前看来,指针的原子更新似乎可以完成这项工作。如果 'a' 是只读的,那么静态指针可以为所有线程工作,只要它们在检查点制作本地副本并且“旧”的 'a' 不被销毁。 - Martin James

0

当然你意识到在你发布的代码中,方法calculate无法访问变量a。如果你想这样工作,你可以编写内联计算函数,并在修改a时使用关键部分:

int a;
#pragma omp parallel
{
   #pragma omp for
   for(int i = 0; i < 4; i++)
   {
       // code of calculate
       #pragma omp critical
       {
           // modify a
       }
       // other code
   }
}

在实际代码中,“a”通过calculate方法中的(回调)指针进行访问。calculate函数非常长,并且在一定程度上,其运行时间无法预测。此外,在每次计算过程中需要多次使用“a”,因此我不能等待整个计算过程完成。因此,我不确定是否适合使用内联。 - Martin
@Martin:我只是建议在修改a的地方放置关键部分,而不是将其放在calculate的末尾。 - Tudor
阅读这些评论后,您很可能需要在calculate方法中添加指令以确保正确更新。 - Bort

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接