【问题标题】:openmp lock reacquired by the same thread while other thread is waiting for it当其他线程正在等待时,同一线程重新获取了 openmp 锁
【发布时间】:2018-06-04 02:14:40
【问题描述】:

我使用倒置锁作为信号量来表示队列更新(注意注释掉的Sleep(1),稍后会用到):

#include <stdio.h>
#include <omp.h>
#include <queue>
#include <stdint.h>
#include <windows.h>

class ThreadLock
{
protected:
  omp_lock_t lock;

public:
  ThreadLock() {
    omp_init_lock(&lock);
  }
  ~ThreadLock() {
    omp_destroy_lock(&lock);
  }

  void acquire() {
    omp_set_lock(&lock);
  }

  void release() {
    omp_unset_lock(&lock);
  }
};

std::queue< uint32_t > g_queue;
ThreadLock g_lock;

void producer()
{
  uint32_t seq = 0;
  g_lock.acquire();
  while (true) {
    Sleep(200);
    #pragma omp critical
      g_queue.push(++seq);
    printf("Produced %u\n", seq);

    g_lock.release();
    //Sleep(1);
    g_lock.acquire();
  }
  g_lock.release();
}

void consumer()
{
  while (true) {
    // Lock if empty
    if (g_queue.empty()) {
      printf("[Consumer] Acquiring lock\n");
      g_lock.acquire();
      g_lock.release();
      printf("[Consumer] Released lock\n");
      if (g_queue.empty()) {
        printf("Still empty\n");
        Sleep(100);
        continue;
      }
    }

    #pragma omp critical
    {
      printf("Consumed %u\n", g_queue.front());
      g_queue.pop();
    }
  }
}

int main(int argc, char* argv[])
{
  #pragma omp parallel sections
  {
    #pragma omp section
      consumer();
    #pragma omp section
      producer();
  }

  return 0;
}

此代码包含一个竞争条件,它会在一段时间后使消费者停止,如下所示:

[Consumer] Acquiring lock
Produced 1
Produced 2
[Consumer] Released lock
Consumed 1
Consumed 2
[Consumer] Acquiring lock
Produced 3
Produced 4
Produced 5
Produced 6
Produced 7
Produced 8
Produced 9
Produced 10
Produced 11
Produced 12
Produced 13
Produced 14
Produced 15
Produced 16
Produced 17
Produced 18
Produced 19

似乎生产者线程在没有上下文切换的情况下匆忙通过释放/获取。美好的。让我们通过取消注释 Sleep(1) 来强制它:

[Consumer] Acquiring lock
Produced 1
[Consumer] Released lock
Consumed 1
[Consumer] Acquiring lock
[Consumer] Released lock
Still empty
[Consumer] Acquiring lock
Produced 2
[Consumer] Released lock
Consumed 2
[Consumer] Acquiring lock
[Consumer] Released lock
Still empty
[Consumer] Acquiring lock
Produced 3
[Consumer] Released lock
Consumed 3

注意到那些Still empty 行了吗?看起来消费者设法在生产者的发布/获取行之间进行了额外的处理。

我知道在消费者线程中添加另一个 Sleep(1) 可以解决问题。但是我觉得代码中这些固定的人为延迟是错误的(Sleep(200) 不算,它仅用于演示目的)。

如何正确地使用 OpenMP 和不使用高于 2.0 的 OpenMP 版本?

【问题讨论】:

  • 我并没有真正遵循您的代码,在您的示例中,生产和消费的实际代码应该放在哪里?

标签: c++ multithreading synchronization openmp race-condition


【解决方案1】:

您的代码中有几个问题。您正在混合 #pragma omp critical 和锁 - 这没有多大意义。你真正想要的是一个锁的组合 - 用于保护队列上的所有操作 - 和一个条件变量 - 以获得有关元素插入的通知。不幸的是,OpenMP 不提供条件变量的原语。您还可以对队列中的元素数量使用计数信号量 - 这在 OpenMP 中也不可用。

然后是饥饿问题,您尝试使用sleep 解决这个问题 - 无论操作系统切换任务的提示如何,它都不会完美。您可以考虑使用 OpenMP 任务 + taskyield(但这不是 OpenMP 2.0)。

归根结底,OpenMP 不太适合这种工作。 OpenMP 更专注于拥有 1 个线程 - 1 个核心映射和分布并行循环。您可以将 OpenMP 线程与 C++11 std::lock / std::condition_variable 结合使用。虽然它可能会在实践中发挥作用,但标准并未正式支持它。

注意:当您保护队列上的操作时,您必须保护所有调用,包括g_queue.empty()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-21
    相关资源
    最近更新 更多