Zależność funkcji openMP
Mam 5 funkcji A, B, C, D, E.
Aby wykonać DI potrzebuję B, C do wykonania, do wykonania EI potrzeba A, D do wykonania.
Próbowałem tego
int main()
{
#pragma omp parallel num_threads(5)
{
long t1 = clock();
int a = 0, b = 0, c = 0, d = 0, e = 0;
int th = omp_get_thread_num();
if (th == 0) {
a += A();
printf("A is finished after %d\n", clock() - t1);
}
if (th == 1) {
b += B();
printf("B is finished after %d\n", clock() - t1);
}
if (th == 2) {
c += C();
printf("C is finished after %d\n", clock() - t1);
}
if (th == 3 && (b == 1 && c == 1)) {
d += D();
printf("D is finished after %d\n", clock() - t1);
}
if (th == 4 && (a == 1 && d == 1)) {
e += E();
printf("E is finished after %d\n", clock() - t1);
}
}
return 0;
}
ale D, E nie wykonał
Wszystkie te funkcje zwracają 1 do teraz w celu debugowania
Odpowiedzi
Zmienne a
, b
, c
i d,
nie może być używany do komunikowania się między wątków, ponieważ wszystkie są prywatne . Dlatego każdy wątek ma swoją prywatną kopię. Co więcej, zwykle nie jest dobrym pomysłem używanie ich do celów synchronizacji.
W twoim kodzie thread=3
nigdy by nie czekał, if (th == 3 && (b == 1 && c == 1))
ponieważ:
b
ic
są prywatne, więcthread=3
mab=0
ic=0
niezależnie od tego, jakie są inne wątki zrobić ich kopie zmiennychb=0
ic=0
.- Nic nie mówi temu wątkowi, aby czekał ( np. Jakiś konstruktor podobny do synchronizacji).
Jeśli chcesz, aby wątki czekały na siebie, użyj omp barrier
zamiast tego. Wszystkie wątki będą musiały wywołać barierę, zanim będą mogły kontynuować obliczenia.
int main()
{
#pragma omp parallel num_threads(5)
{
long t1 = clock();
int a = 0, b = 0, c = 0, d = 0, e = 0;
int th = omp_get_thread_num();
if (th == 0) {
a += A();
printf("A is finished after %d\n", clock() - t1);
}
if (th == 1) {
b += B();
printf("B is finished after %d\n", clock() - t1);
}
if (th == 2) {
c += C();
printf("C is finished after %d\n", clock() - t1);
}
// Threads will wait for each other here
#pragma omp barrier
if (th == 3) {
d += D();
printf("D is finished after %d\n", clock() - t1);
}
// Threads will wait for each other here
#pragma omp barrier
if (th == 4) {
e += E();
printf("E is finished after %d\n", clock() - t1);
}
}
return 0;
}
Bardziej wyrafinowanym podejściem byłoby użycie zadań z zależnościami, funkcja wydana w standardzie OpenMP 4.0. Jest już fajne wyjaśnienie, jak ta funkcja działa w tym wątku.
int a = 0, b = 0, c = 0, d = 0, e = 0;
#pragma omp parallel num_threads(5) shared(a, b, c, d)
{
#pragma omp single nowait
{
long t1 = clock();
int th = omp_get_thread_num();
#pragma omp task depend (out:a)
{
a += A();
printf("A is finished after %d\n", clock() - t1);
}
#pragma omp task depend (out:b)
{
b += B();
printf("B is finished after %d\n", clock() - t1);
}
#pragma omp task depend (out:c)
{
c += C();
printf("C is finished after %d\n", clock() - t1);
}
#pragma omp task depend (in:a, b) depend(out:d)
{
d += D();
printf("D is finished after %d\n", clock() - t1);
}
#pragma omp task depend (in:a, b)
{
e += E();
printf("E is finished after %d\n", clock() - t1);
}
}
}
return 0;
}
Właściwym rozwiązaniem OpenMP byłoby użycie zadań z zależnościami danych:
#pragma omp parallel num_threads(3)
#pragma omp single
{
double t1 = omp_wtime();
int a = 0, b = 0, c = 0, d = 0, e = 0;
#pragma omp task shared(a) depend(out: a)
{
a += A();
printf("A is finished after %f\n", omp_wtime() - t1);
}
#pragma omp task shared(b) depend(out: b)
{
b += B();
printf("B is finished after %f\n", omp_wtime() - t1);
}
#pragma omp task shared(c) depend(out: c)
{
c += C();
printf("C is finished after %f\n", omp_wtime() - t1);
}
#pragma omp task shared(b,c,d) depend(in: b,c) depend(out: d)
{
d += D();
printf("D is finished after %f\n", omp_wtime() - t1);
}
#pragma omp task shared(a,d,e) depend(in: a,d)
{
e += E();
printf("E is finished after %f\n", omp_wtime() - t1);
}
}
Tutaj zadanie A
jest oznaczone jako producent dla wartości a
with, depend(out: a)
a zadanie D
jest oznaczone jako producent d
z depend(out: d)
. Zadanie E
jest oznaczone jako konsument tych dwóch wartości za pomocą depend(in: a,d)
. Podążając za zależnościami wyjściowymi (producent) i wejściowymi (konsument), środowisko uruchomieniowe OpenMP buduje wykonawczy DAG (skierowany graf acykliczny), który informuje go o prawidłowej kolejności wykonywania wszystkich zadań. Nie potrzebujesz też pięciu wątków - wystarczą trzy.
Posiadanie kodu zadań wewnątrz single
konstrukcji jest bardzo idiomatycznym OpenMP.
Zależności zadań zostały wprowadzone przez OpenMP 4.0 w 2013 roku, więc każdy nowoczesny kompilator z wyjątkiem MSVC ++ powinien zapewniać obsługę tej funkcji.