2013-04-21 213 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);等待複製線程發出信號。