如何确保多线程编程中的执行顺序?

时间:2013-04-21 00:35:34

标签: c++ c multithreading openmp

以下多线程代码中是否有任何问题?它总是给我不一致的结果。看起来编译器优化可能会在数据处理行之前移动标志设置行,从而导致严重的数据争用情况。

有没有办法在不增加障碍的情况下避免这种情况?

#pragma omp parallel num_threads(16)

int tid=omp_get_thread_num();

if (tid<8)
{
   copydata(arrayofPtrs[tid]);

   flag[tid]=1;//flag is an array of volatile int where its initial values are all 0.

}
else
{
   for (int i=0; i<100000; ++i)
   {
     if (flag[tid-8]==1)
      {
       processingdata(arrayofPtrs[tid-8]);
       break;
       }
     else
       Sleep(200);
   };
};

2 个答案:

答案 0 :(得分:1)

您可以在处理线程的标记测试周围使用循环,以便它们将旋转锁定标志,直到它被设置为止。但是,这部分代码看起来是顺序的,那么为什么要使用多个线程进行复制/处理?您可以使用线程进行复制,然后继续使用相同的线程进行处理。

答案 1 :(得分:0)

据我所知,除非数据已被复制,否则数据处理无法继续,因此并行处理是没有意义的 - 处理线程将浪费CPU时间等待复制线程完成并设置标志。那么为什么不在一个块中合并两个操作:

#pragma omp parallel num_threads(8)
{
   int tid = omp_get_thread_num();

   copydata(arrayofPtrs[tid]);
   processingdata(arrayofPtrs[tid]);
}

如果您仍想保留原始想法,可能是复制和处理都以异步方式重复发生,那么您需要使用Open MP atomic操作同步对该标志的访问:

#pragma omp parallel num_threads(16)
{
   int tid = omp_get_thread_num();

   if (tid < 8)
   {
      copydata(arrayofPtrs[tid]);

      #pragma omp atomic write
      flag[tid] = 1;//flag is an array of volatile int where its initial values are all 0.
   }
   else
   {
      for (int i = 0; i < 100000; ++i)
      {
         #pragma omp atomic read
         int ready = flag[tid-8];
         if (ready == 1)
         {
            processingdata(arrayofPtrs[tid-8]);
            break;
         }
         else
            Sleep(200);
      }
   }
}

对于大多数编译器,atomic构造具有副作用,即引用的变量变为易失性。您还可以使用flush显式更新内存视图:

#pragma omp atomic write
flag[tid] = 1;
#pragma omp flush(flag)

readwrite条款仅在最近的OpenMP版本上受支持。这个Sleep()看起来像是在使用Win32 API,因此可能使用MSVC,它不支持readwrite修饰符,因为它只实现OpenMP 2.0,但代码应该编译和工作如预期的那样。

在没有繁忙循环的情况下执行此操作的另一种方法是使用OpenMP锁。初始化一组锁,在复制线程中获取它们,然后让每个处理线程等待获取锁:

omp_lock_t locks[8];
for (int i = 0; i < 8; i++)
   omp_init_lock(&locks[i]);

#pragma omp parallel num_threads(16)
{
    int tid = omp_get_thread_num();

    // Have the first 8 threads acquire the locks
    if (tid < 8)
       omp_set_lock(&locks[tid]);

    #pragma omp barrier

    // Now locks are set and processing can continue

    if (tid < 8)
    {
       copydata(arrayofPtrs[tid]);
       omp_unset_lock(&locks[tid]);
    }
    else
    {
       omp_set_lock(&locks[tid-8]);
       processingdata(arrayofPtrs[tid-8]);
       omp_unset_lock(&locks[tid-8]);
    }
 }

 for (int i = 0; i < 8; i++)
    omp_destroy_lock(&locks[i]);

您也可以使用POSIX信号量的Win32事件而不是OpenMP锁来实现相同的功能。这种方法的优点是您不需要在等待设置标志时显式循环。相反,omp_set_lock()调用将阻塞,直到复制线程释放其锁定。使用Win32事件,您可以使用WaitForSingleObject(hEvent, INFINITE);等待复制线程发出信号。