A seguir tem-se alguns exemplos para ilustrar o que foi mostrado. Todos os exemplos foram feitos utilizando a linguagem C em ambiente windows.
Esse exemplo apenas cria uma região paralela e executa um printf com cada thread. Porém isso irá gerar um problema, visto na sequência.
int main(int argc, char* argv[])
{
int id;
int number_threads;
printf("\tOla 1 - Antes da regiao paralela\n\n");
#pragma omp parallel
{
id = omp_get_thread_num(); /* recupera o numero que identifica a thread */
number_threads = omp_get_num_threads(); /* recupera o numero de threads */
printf("\tExecutando a thread %d de um total de %d\n", id, number_threads);
}
printf("\n\tOla 2 - Apos a regiao paralela\n\n");
}
Saída:
Note que as threads não estão em ordem, isso ocorre justamente por estarem executando em paralelo(todas ao mesmo tempo). E foram executadas 4 threads, porque? Se o número de threads não for definido, ele utilizará a quantidade de processadores disponíveis no computador.
O número de threads utilizado pode ser alterado de três formas, alterando a variável de ambiente OMP_NUM_THREADS, utilizando a chamada da função set_omp_num_threads ou utilizando a cláusula num_threads. Em todos os casos, o mais respeitado será a cláusula, em seguida a chamada da função e, por ultimo, a variável de ambiente. É mostrado abaixo as formas de alterar o número de threads.
Variável de ambiente
Como mostra a imagem, a variável foi alterada para 32, após isso, basta executar o código criado, se já foi compilado anteriormente, não é necessário compilar novamente.
Chamada de função e cláusula
int main(int argc, char* argv[])
{
omp_set_num_threads(16); /* define o numero de threads a ser utilizado */
int id;
int number_threads;
printf("\tOla 1 - Antes da regiao paralela\n\n");
#pragma omp parallel num_threads(8)
{
id = omp_get_thread_num(); /* recupera o numero que identifica a thread */
number_threads = omp_get_num_threads(); /* recupera o numero de threads */
printf("\tExecutando a thread %d de um total de %d\n", id, number_threads);
}
printf("\n\tOla 2 - Apos a regiao paralela\n\n");
}
Saída
Note que foram executadas 8 threads, o que foi especificado na cláusula, mesmo que na variável OMP_NUM_THREADS esteja atribuído o valor 32 e na função omp_set_num_threads esteja definido o valor 16.
Veja também que há algo de errado(o problema comentado anteriormente). Nesse exemplo, ocorre a condição de corrida, ou seja, duas threads acessam suas regiões críticas ao mesmo tempo, e não queremos que isso ocorra. Isso pode ou não ocorrer, dependendo da velocidade em que as threads são executadas.
Esse problema pode ser resolvido de várias formas diferentes, porém, as duas formas mais básicas de fazer isso é declarando as variáveis dentro da região paralela ou utilizando a cláusula private. Como mostra os exemplos a seguir.
Declarando dentro da região paralela.
int main(int argc, char* argv[])
{
printf("\tOla 1 - Antes da regiao paralela\n\n");
#pragma omp parallel num_threads(8)
{
int id = omp_get_thread_num(); /* recupera o numero que identifica a thread */
int number_threads = omp_get_num_threads(); /* recupera o numero de threads */
printf("\tExecutando a thread %d de um total de %d\n", id, number_threads);
}
printf("\n\tOla 2 - Apos a regiao paralela\n\n");
}
Utilizando a cláusula private.
int main(int argc, char* argv[])
{
printf("\tOla 1 - Antes da regiao paralela\n\n");
int id;
int number_threads;
#pragma omp parallel num_threads(8) private(id, number_threads)
{
id = omp_get_thread_num(); /* recupera o numero que identifica a thread */
number_threads = omp_get_num_threads(); /* recupera o numero de threads */
printf("\tExecutando a thread %d de um total de %d\n", id, number_threads);
}
printf("\n\tOla 2 - Apos a regiao paralela\n\n");
}
Na sequência temos um exemplo da utilização do constructor for, para realizar operações entre vetores.
int main(int argc, char* argv[])
{
const int size = 10;
int a[size], b[size], c[size];
int i;
for(i = 0;i < size;i++)
{
a[i] = 2;
b[i] = 5;
}
#pragma omp parallel num_threads(4)
{
int id = omp_get_thread_num();
#pragma omp for
for(i = 0;i < size; i++)
{
c[i] = a[i] + b[i];
printf("A thread %d somou a posicao %d, e deu o resultado %d\n", id, i, c[i]);
}
}
return 0;
}
Saída:
O construct for distribui as posições do vetor igualmente entre as threads, fazendo com que o programador não precise se preocupar em dividir manualmente.