Dependência de funções openMP
Tenho 5 funções A, B, C, D, E.
Para executar DI é necessário que B, C seja executado, para executar EI é necessário que A, D seja executado.
Eu tentei isso
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;
}
mas D, E não executou
Todas essas funções retornam 1 até agora para fins de depuração
Respostas
As variáveis a
, b
, c
e d,
não pode ser usado para comunicar entre threads, porque eles são todos privados . Portanto, cada segmento tem sua própria cópia privada deles. Além disso, normalmente não é uma boa idéia usá-los para fins de sincronização.
Em seu código thread=3
nunca esperaria if (th == 3 && (b == 1 && c == 1))
porque:
b
ec
são privados, assimthread=3
comob=0
ec=0
independentemente do que os outros threads tenham feito com suas cópias das variáveisb=0
ec=0
.- Não há nada que diga que a thread deve esperar ( por exemplo, algum construtor de sincronização).
Se você quiser que os threads esperem uns pelos outros, use o omp barrier
. Todos os threads terão que chamar a barreira antes de poderem prosseguir com seu cálculo.
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;
}
Uma abordagem mais sofisticada seria usar tarefas com dependências, um recurso lançado no padrão OpenMP 4.0. Já existe uma boa explicação sobre como esse recurso funciona neste tópico.
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;
}
Uma solução OpenMP adequada seria usar tarefas com dependências de dados:
#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);
}
}
Aqui, tarefa A
é marcada como produtor para o valor de a
com depend(out: a)
e tarefa D
é marcada como produtor de d
com depend(out: d)
. A tarefa E
é marcada como consumidora desses dois valores com depend(in: a,d)
. Seguindo as dependências de saída (produtor) e entrada (consumidor), o tempo de execução do OpenMP constrói um DAG de execução (gráfico acíclico direcionado) que informa a ordem correta de execução de todas as tarefas. Você também não precisa de cinco threads - três são suficientes.
Ter o código de tarefas dentro de uma single
construção é um OpenMP muito idiomático.
As dependências de tarefas foram introduzidas pelo OpenMP 4.0 em 2013, portanto, qualquer compilador moderno, exceto MSVC ++, deve fornecer suporte para esse recurso.