[英]OpenMP integer copied after tasks finish
我不知道這是否記錄在任何地方,如果有的話,我很想參考它,但是我在使用 OpenMP 時發現了一些意外行為。 我在下面有一個簡單的程序來說明這個問題。 在這里,我將告訴我希望程序做什么:
正如您將看到的,線程之間共享的計數器沒有為第二個線程正確更改。 但是,如果我將計數器改為 integer 引用,我會得到預期的結果。 這是一個簡單的代碼示例:
#include <mutex>
#include <thread>
#include <chrono>
#include <iostream>
#include <omp.h>
using namespace std;
using std::this_thread::sleep_for;
using std::chrono::milliseconds;
const int sleep_amount = 2000;
int main() {
int counter = 0; // if I comment this and uncomment the 2 lines below, I get the expected results
/* int c = 0; */
/* int &counter = c; */
omp_lock_t mut;
omp_init_lock(&mut);
int counter_1, counter_2;
#pragma omp parallel
#pragma omp single
{
#pragma omp task default(shared)
// The first task just increments the counter 3 times
{
while (counter < 3) {
omp_set_lock(&mut);
counter += 1;
cout << "increasing: " << counter << endl;
}
}
#pragma omp task default(shared)
{
sleep_for(milliseconds(sleep_amount));
// While sleeping, counter is increased to 1 in the first task
counter_1 = counter;
cout << "counter_1: " << counter << endl;
omp_unset_lock(&mut);
sleep_for(milliseconds(sleep_amount));
// While sleeping, counter is increased to 2 in the first task
counter_2 = counter;
cout << "counter_2: " << counter << endl;
omp_unset_lock(&mut);
// Release one last time to increment the counter to 3
}
}
omp_destroy_lock(&mut);
cout << "expected: 1, actual: " << counter_1 << endl;
cout << "expected: 2, actual: " << counter_2 << endl;
cout << "expected: 3, actual: " << counter << endl;
}
這是我的 output:
increasing: 1
counter_1: 0
increasing: 2
counter_2: 0
increasing: 3
expected: 1, actual: 0
expected: 2, actual: 0
expected: 3, actual: 3
gcc 版本:9.4.0
其他發現:
不允許從另一個線程解鎖互斥鎖。 這樣做會導致未定義的行為。 在這種情況下,一般的解決方案是使用信號量。 等待條件也有幫助(關於現實世界的用例)。 引用OpenMP 文檔(請注意,幾乎所有互斥鎖實現都共享此約束,包括 pthreads):
訪問不在鎖定 state 中的鎖或不屬於包含通過任一例程調用的任務的鎖的程序是不合格的。
通過任一例程訪問不在未初始化 state 中的鎖的程序是不合格的。
而且,這兩個任務可以在同一個線程或不同線程上執行。 除非您告訴 OpenMP 使用依賴項來這樣做,否則您不應假設它們的調度。 在這里,運行時串行執行任務是完全兼容的。 您需要使用OpenMP 部分,以便多個線程執行不同的部分。 此外,通常認為在任務中使用鎖是一種不好的做法,因為運行時調度程序不知道它們。
最后,在這種情況下您不需要鎖:原子操作就足夠了。 幸運的是, OpenMP 支持原子操作(以及 C++)。
請注意,由於memory 屏障,鎖保證了多線程中 memory 訪問的一致性。 實際上,對互斥體的解鎖操作會導致釋放 memory 屏障,使其他線程可以看到寫入。 來自另一個線程的鎖執行獲取 memory 屏障,強制讀取在鎖之后完成。 如果未正確使用鎖定/解鎖,則 memory 訪問的方式不再安全,這會導致某些變量無法從其他線程更新。 更一般地說,這也往往會產生競爭條件。 因此,簡而言之,不要那樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.