【问题标题】:Implementing counting semaphores using OpenMP lock routines使用 OpenMP 锁定例程实现计数信号量
【发布时间】:2015-12-13 23:12:39
【问题描述】:

我正在尝试使用 OpenMP 的锁定结构来实现 counting semaphores,但是在尝试在关键区域内使用 omp_set_lock() 时遇到了问题(程序挂起)。

我正在使用一个简单的生产者-消费者程序来测试实现。这是我想出的:

#include <omp.h>
#include <stdio.h>

#define N 50

int semaphore_count = 0;
omp_lock_t semaphore_lock;

int A[N];

void semaphore_increment()
{
  int my_id = omp_get_thread_num();

  #pragma omp critical
  {
    printf("[%lf][%d] semaphore_count %d --> %d\n", omp_get_wtime(), my_id,
           semaphore_count, semaphore_count + 1);

    semaphore_count++;

    if(semaphore_count == 1) {
      // Semaphore was previously locked, so unlock it.
      printf("[%lf][%d] Releasing lock.\n", omp_get_wtime(), my_id);
      omp_unset_lock(&semaphore_lock);
    }
  }
}

void semaphore_decrement()
{
  int my_id = omp_get_thread_num();
  #pragma omp critical
  {
    printf("[%lf][%d] semaphore_count: %d\n", omp_get_wtime(), my_id,
           semaphore_count);

    if (semaphore_count == 0) {
      printf("[%lf][%d] Sleeping\n", omp_get_wtime(), my_id);
      omp_set_lock(&semaphore_lock);
    }
    else {
      printf("[%lf][%d] Working\n", omp_get_wtime(), my_id);
      // Creating a critical region here instead of in the beginning of
      // the function solves the problem.
      // #pragma omp critical
      // {
        semaphore_count--;
      // }
      if (semaphore_count == 0) {
        omp_set_lock(&semaphore_lock);
      }
    }
  }
}

void produce() {
  for (int i = 0; i < N; ++i) {
    A[i] = i;
    #pragma omp flush
    semaphore_increment();
  }
}

void consume() {
  int sum = 0;
  for (int i = 0; i < N; ++i) {
    semaphore_decrement();
    sum += A[i];
  }

  printf("Sum is: %d\n", sum);
}

int main() {

  omp_init_lock(&semaphore_lock);
  omp_set_lock(&semaphore_lock);

  #pragma omp parallel
  {
    #pragma omp single nowait
    produce();

    #pragma omp single nowait
    consume();
  }

  omp_destroy_lock(&semaphore_lock);
  return 0;
}

每次消费者线程进入睡眠状态时,此版本的程序都会挂起。如果我修改它以将关键区域减少到代码的较小部分(如程序中的注释所示),那么它就可以工作。

我不明白的是:为什么会这样?似乎只增加信号量的生产者线程停止运行,然后一切都挂起,但我不明白为什么。

【问题讨论】:

  • 将某些东西放入临界区并不会使其相对于 不同 临界区中的某些东西具有原子性。你有竞争条件。在减量周围放置一个小的关键部分可能会导致减量被编译为原子读取-修改-写入,因此它意外地是原子的。这不会使您的代码正确,但可能会使比赛窗口变小,因此您的测试不再显示错误。
  • OpenMP 拥有原子性已有一段时间了。它们可用于 inc 和 dec 等。
  • @EOF 的初始评论是错误:所有具有相同名称的critical 区域(未命名的区域都一起考虑)是互斥的。当前 OpenMP 标准的第 2.13.2 章中描述了确切的语义,但本质上,这就是它归结为您的内容。因此,至少在这方面,您的代码是正确的。我还不知道哪里出了问题,但这绝对不是这部分。
  • @Gilles:这太疯狂了,但你说得很对。注意。
  • 您的程序不合格。在给定线程中设置锁会使该线程成为锁的所有者。在 OpenMP 中不允许取消设置由另一个线程拥有的锁,这会导致未定义的行为。这就是为什么您的代码总是与英特尔 OpenMP 运行时一起工作,并且总是在我的系统上与 GCC 发生死锁。

标签: c parallel-processing openmp producer-consumer


【解决方案1】:

这个答案是错误(见 cmets)。我把它留在这里作为如何做的一个例子。


正如EOF 在 cmets 中所指出的,问题中显示的程序是错误的,因为它有一个关于更新 semaphore_count 变量的竞争条件,因为这可能同时发生在两个不同的关键部分。

我最终将函数 semaphore_incrementsemaphore_decrement 替换为以下函数,它可以根据作为 operation 传入的值增加或减少信号量值。

void semaphore_update(int operation) {
  int my_id = omp_get_thread_num();
  int set_lock = 0;

  #pragma omp critical
  {
    if (operation == 0) { // Decrement operation
      if (semaphore_count == 0) {
        set_lock = 1;
      }
      else {
        semaphore_count--;
        if (semaphore_count == 0) {
          // Locking here won't actually lock the current thread, only set
          // the semaphore so that the *next* thread will be put to sleep.
          set_lock = 1;
        }
      }
    }
    else { // Increment operation
      semaphore_count++;
      if(semaphore_count == 1) {
        // Semaphore was previously locked, so unlock it.
        omp_unset_lock(&semaphore_lock);
      }
    }
  }

  // The actual call to omp_set_lock has to happen outside the critical region
  // otherwise any threads trying to unlock the semaphore won't be able to
  // get access to the critical region.
  if (set_lock) {
    omp_set_lock(&semaphore_lock);
  }
}

【讨论】:

  • 不幸的是,将omp_set_lock() 保持在临界区之外会导致竞争条件:考虑如果递减线程在离开临界区后立即被调度程序中断,并且递增线程执行会发生什么情况同时过去omp_unset_lock()。递减线程不知道它应该唤醒,因为它甚至还没有等待。它最终会永远等待。锁不是正确的(只有持有锁的线程可以解锁它),你需要一个条件变量之类的东西。
猜你喜欢
  • 2017-10-03
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 2010-10-30
  • 1970-01-01
  • 1970-01-01
  • 2013-08-16
  • 2012-09-21
相关资源
最近更新 更多