2013-04-21 175 views
2

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

有没有办法避免这种情况,而不添加屏障?

#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); 
    }; 
}; 
+0

是的,允许编译器移动指令的顺序。这明确是为语言添加障碍的原因 - 让程序员确保所需的顺序。在这种情况下,挥发性无助。 – 2013-04-21 00:43:40

+0

@PeterR:我的代码中有太多的障碍我真的害怕死锁等等,顺便说一句,我添加一个volatile关键字的原因是为了防止编译器优化代码将标志数据加载到寄存器中。 – user2188453 2013-04-21 00:50:04

+6

易失性不会做你认为它的作用。您需要使用C++ 11或c11原子或omp屏障。 volatile会禁用寄存器分配,但不会禁止编译器在设置标志后移动copydata()调用。 volatile用于访问设备寄存器,它没有并行代码。 – 2013-04-21 01:53:49

回答

1

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

+0

那么,迈克尔已经编辑了我的主题,太糟糕了一些信息被编辑出来,实际上只是显示algorthim概念的代码片段,而不是真正的代码,这些代码很长,而且还有很多其他的东西。 – user2188453 2013-04-21 00:48:07

+0

仍然,我不明白你为什么要在不同的线程中进行复制和处理。但只要你只设置一个方向的标志形式,那么你可以使用繁忙的等待。 – perreal 2013-04-21 00:57:53

+1

我没有碰你的代码,@ user2188453;我只是复制你的散文。您可以查看编辑历史记录以查看我所做的操作 - 单击“已编辑的X分钟前”链接。 – 2013-04-21 01:00:20

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信号,而不是OpenMP的锁同样使用Win32事件。这种方法的优点是,您无需在等待标志设置时显式循环。而是omp_set_lock()调用会阻塞,直到复制线程释放它的锁。使用Win32事件,您可以使用WaitForSingleObject(hEvent, INFINITE);等待复制线程发出信号。