gcc 任务完成后复制OpenMP整数

gajydyqb  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(144)

我不知道这是否在任何地方被记录下来,如果有的话,我希望能有一个参考,但是我发现在使用OpenMP时出现了一些意外的行为。下面我有一个简单的程序来说明这个问题。在这里,我将以点的形式告诉你我希望程序做什么:

  • 我想有2个线程
  • 它们共享一个整数
  • 第一个线程递增整数
  • 第二个线程读取整数
  • 递增一次后,外部进程必须告知第一个线程继续递增(通过互斥锁)
  • 第二个线程负责解锁该互斥锁

正如您将看到的,线程之间共享的计数器没有为第二个线程正确更改。但是,如果我将计数器改为整数引用,我将得到预期的结果。下面是一个简单的代码示例:

#include <mutex>
#include <thread>
#include <chrono>
#include <iostream>
#include <omp.h>

using namespace std;
using std::this_thread::sleep_for;
using std::chrono::milliseconds;

const int sleep_amount = 2000;

int main() {

  int counter = 0; // if I comment this and uncomment the 2 lines below, I get the expected results
  /* int c = 0; */
  /* int &counter = c; */

  omp_lock_t mut;
  omp_init_lock(&mut);
  int counter_1, counter_2;

#pragma omp parallel
#pragma omp single
  {
#pragma omp task default(shared)
// The first task just increments the counter 3 times
    {
      while (counter < 3) {
        omp_set_lock(&mut);
        counter += 1;
        cout << "increasing: " << counter << endl;
      }
    }
#pragma omp task default(shared)
    {
      sleep_for(milliseconds(sleep_amount));
      // While sleeping, counter is increased to 1 in the first task
      counter_1 = counter;
      cout << "counter_1: " << counter << endl;

      omp_unset_lock(&mut);
      sleep_for(milliseconds(sleep_amount));
      // While sleeping, counter is increased to 2 in the first task
      counter_2 = counter;
      cout << "counter_2: " << counter << endl;
      omp_unset_lock(&mut);
      // Release one last time to increment the counter to 3
    }
  }
  omp_destroy_lock(&mut);

  cout << "expected: 1, actual: " << counter_1 << endl;
  cout << "expected: 2, actual: " << counter_2 << endl;
  cout << "expected: 3, actual: " << counter << endl;
}

以下是我的输出:

increasing: 1
counter_1: 0
increasing: 2
counter_2: 0
increasing: 3
expected: 1, actual: 0
expected: 2, actual: 0
expected: 3, actual: 3

gcc版本:9.4.0
其他发现:

  • 如果我使用OpenMP的“section”而不是“tasks”,我也会得到预期的结果。
  • 如果我使用posix信号量,这个问题也会继续存在。
hgb9j2n6

hgb9j2n61#

这是不允许从另一个线程解锁互斥体的。这样做会导致未定义的行为。在这种情况下,一般的解决方案是使用信号量。等待条件也会有所帮助(关于真实世界的用例)。引用OpenMP documentation(注意,几乎所有互斥体实现都共享此约束,包括pthreads):
如果某个程序访问的锁不处于锁定状态,或者不属于包含通过任一例程进行的调用的任务,则该程序是不相容的。
通过任一例程访问未处于未初始化状态的锁的程序是不相容的。
此外,这两个任务可以在同一线程或不同线程上执行。您不应对它们的调度作任何假设,除非您告诉OpenMP这样做具有依赖性。在这里,运行时串行执行任务是完全符合要求的。您需要使用OpenMP段,以便多个线程执行不同的段。此外,在任务中使用锁通常被认为是不好的做法,因为运行时调度程序并不知道它们。
最后,在这种情况下不需要锁:一个原子操作就足够了。幸运的是,OpenMP supports atomic operations(以及C++)。

附加注解

请注意,由于内存屏障,锁定可保证多个线程中内存访问的一致性。实际上,互斥锁上的解锁操作会导致释放内存屏障,使写入操作对其他线程可见。来自另一个线程的锁定会导致获取内存屏障,强制读取操作在锁定后完成。如果未正确使用锁定/解锁,内存访问的方式不再安全,这会导致一些变量无法从其他线程更新。更普遍地说,这也会产生竞争条件。因此,简单地说,不要这样做。

相关问题