• Nenhum resultado encontrado

15- AulaIHS-OpenMP

N/A
N/A
Protected

Academic year: 2021

Share "15- AulaIHS-OpenMP"

Copied!
70
0
0

Texto

(1)

Interface Hardware-Software

(2)

Aumento de Desempenho dos Processadores

Ao longo dos anos, processadores aumentaram o poder de

processamento

– Aumento da frequencia

– Pipeline

– Superescalar

Mais recentemente, o aumento do desempenho foi

alcançado através do suporte em HW à execução paralela

(real) de diferentes tarefas

– Hardware multithreading

– Multicores

(3)

Hardware Multi-Threading

Abordagem multi-thread

– Aumentar utilização de recursos de hardware permitindo que

múltiplas threads possam executar virtualmente de forma

simultânea em único processador

– Compartilhamento de unidades funcionais

Objetivos

– Melhor utilização de recursos (cache e unidades de execução

são compartilhadas entre threads)

– Ganho de desempenho (em média 20%)

(4)

Simultaneous Multi-Threading (SMT)

SMT

é uma política de multi-threading para processadores

superescalares com escalonamento dinâmico de threads e

instruções

– Escalonamento de instruções de threads diferentes

– Instruções de threads diferentes podem executar

paralelamente desde que haja unidades funcionais livres

– Explora paralelismo ao nível de instrução (

I

nstruction

L

evel

P

arallelism -

ILP

)

– Explora paralelismo ao nível de threads (

T

hread

L

evel

P

arallelism –

TLP

)

Hyper-Threading é um caso de de SMT proposto pela Intel

– Disponível em processadores Xeon, Pentium 4- HT, Atom,

Intel i7

(5)

Multicores

Microprocessadores multicores

– Mais de um processador por chip

Requer programação paralela

para uma melhoria efetiva

– Hardware executa múltiplas

instruções paralelamente

Transparente para o

programador

– Difícil de

Programar visando

desempenho

(6)

Chapter 7 — Multicores, Multiprocessors, and Clusters — 6

Processamento Paralelo - Hardware e Software

Software sequencial/concorrente pode executar

em hardware serial/paralelo

Desafio: fazer uso efetivo de hardware paralelo

Software Sequencial Concorrente Hardware Serial Multiplicação de matrizes em Java no Intel Pentium 4 Windows Vista no Intel Pentium 4 Paralelo Multiplicação de matrizes em Java no AMD Athlon Phenom II Windows Vista no AMD Athlon Phenom II

(7)

Programação Paralela

Desenvolver software para executar em HW paralelo

Necessidade de melhoria significativa de desempenho

– Senão é melhor utilizar processador com único core rápido,

pois é mais fácil de escrever o código

Dificuldades

– Particionamento de tarefas (Balanceamento de carga)

– Coordenação

(8)

Modelos de Comunicação

Nos computadores atuais, 2 modelos de comunicação

entre núcleos são utilizados:

– Memória Compartilhada (Mais utilizada em arquiteturas

convencionais)

– Passagem de Mensagens (Memória Distribuída)

(9)

Chapter 7 — Multicores, Multiprocessors, and Clusters — 9

Memória Compartilhada

Shared memory multiprocessor

Mesmo espaço de memória é compartilhado pelos

diferentes processadores

Comunicação

é

feita

através

de

variáveis

compartilhadas

(10)

Comunicação com Memória Compartilhada

Variáveis compartilhadas contêm os dados que são

comunicados entre um processador e outro

Processadores acessam variáveis via loads/stores

Acesso a estas variáveis deve ser controlado

(sincronizado)

– Uso de

locks (semáforos)

– Apenas um processador pode adquirir o lock em um

determinado instante de tempo

(11)

Chapter 7 — Multicores, Multiprocessors, and Clusters — 11

Passagem de Mensagens

Message Passing

Processadores

compartilham

dados

enviando

explicitamente os dados (mensagem)

Comunicação é feita através de primitivas de

comunicação (send e receive)

(12)

Comunicação com Message Passing

Cada processador tem seu espaço de endereçamento

privado

Processadores mandam mensagens utilizando esquema

parecido ao de acessar um dispositivo de Entrada/Saída

– Processador que aguarda mensagem é interrompido

quando mensagem chega

Sincronização de acesso aos dados é feita pelo próprio

programa

– Programador é responsável para estabelecer os pontos de

comunicação com as primitivas send e receive

(13)

OpenMP

OpenMP( Open Multi-Processor) é uma API que dá

suporte ao modelo de programação (comunicação)

paralelo de memória compartilhada

Padronizada pelo consórcio OpenMP ARB(OpenMP

Architecture Review Board)

– AMD, Intel, Cray, HP, Oracle, Nvidia, NEC, etc

– Roda em Linux, Windows, Mac OS X, Solaris, etc

(14)

Camadas de SW e OpenMP

(15)

Objetivos de OpenMP

Padronização da utilização do modelo de programação de

memória compartilhada para diferentes plataformas

Provimento de uma camada de SW enxuta para programar

plataformas com memória compartilhada

– Poucas diretivas e funções para programação paralela

Facilidade de uso deste modelo de programação

– Permite a paralelização incremental de um código sequencial

com poucas diretivas

Portabilidade

– API em C, C++ e Fortran

– Maioria das plataformas no mercado aceitam Linux e

Windows

(16)

Suporte ao Modelo de Memória Compartilhada

 Threads tem acesso a mesma memória global compartilhada

 Dados podem ser compartilhados ou privados

 Dados privados podem ser apenas acessados pelas threads que são as donas  Transferência de dados são

transparentes ao programador

 Sincronização acontece, mas boa parte é implícita

(17)

Paralelismo Suportado por OpenMP

Paralelismo através de threads

– Quantidade de threads não precisa ser igual a quantidade

de cores

Programador consegue explicitar que partes do código

são paralelizados

– Pode explicitar vários níveis de paralelismo

Utiliza o modelo de execução paralela fork-join

– Uma thread master pode criar várias threads para executar

em paralelo

– Quando todas as threads filhas completam as tarefas,

sincronizam e terminam a execução, deixando a thread

master

(18)

Modelo Fork e Join

Fonte:

(19)

Estrutura Típica de Um Programa OpenMP

Região Paralela

Região

Sequencial

Pode vir seguido de mais regiões paralelas ou

sequenciais

Início

Fim

 Thread master (inicial,0) é onde a execução do programa se inicia e termina

 Thread master cria um time (conjunto) de threads que vão executar algo em paralelo (fork)

 Quando todos os threads de um time chegam ao final da região paralela, todos menos o master terminam (join)

(20)

Componentes de OpenMP

Fonte:

(21)

Exemplo de OpenMP: Hello World Paralelo

#include <stdio.h>

#include <stdlib.h> #include <omp.h> int main(){

int nthreads, tid;

/* Região Paralela, é dado um fork para um time de threads*/ #pragma omp parallel private(nthreads, tid)

{

/* Obtém o número da thread*/ tid = omp_get_thread_num();

printf("Hello World da thread = %d\n", tid); /* Somente o thread master faz isto */

if (tid == 0){

nthreads = omp_get_num_threads();

printf(“Quantidade de threads = %d\n", nthreads); }

} /* No fim é dado um join */ }

Diretiva que indica o começo de uma região paralela

(22)

Saída da Execução do Hello World Paralelo

4 threads criadas que

(23)

Algumas Considerações Sobre a

Paralelização em OpenMP

A quantidade de threads criadas pode ser configurada

– Variáveis de ambiente, diretivas e chamadas de funções

A ordem de execução das threads é determinada pelo S.O

- A cada execução a ordem pode mudar

Se threads diferentes acessam o mesmo dado, OpenMP não

garante sozinho o acesso na ordem correta do dado

– Programador deve explicitar onde haverá a sincronização

(24)

O Que Deve Ser Feito para Usar OpenMP?

No caso de C/C++ ter um compilador que possua a

biblioteca libgomp

– Importante ter também a biblioteca libpthread (Linux) ou

libwinpthread (Windows)

Colocar no código um

#include <omp.h>

Configurar a compilação e a link-edição

(25)

Configurando a Compilação no CodeBlocks

gcc deve compilar com esta opção

(26)

Configurando a Link-Edição no CodeBlocks

Deve incluir o caminho da biblioteca na link-edição

(27)

Diretivas de OpenMP

Boa parte das construções de OpenMP são diretivas de

compilação

– Instruem a forma de execução do código como por exemplo:

paralelização, serialização e sincronização

– Se não colocar a opção de compilação –fopenmp, a diretiva é

simplesmente ignorada

Uma região paralela sob a diretiva é delimitada por chaves

{ }

– Possibilidade de haver outras diretivas dentro das chaves

Forma Geral:

(28)

Tipos de Diretivas de OpenMP

Básica de Paralelismo

– parallel

Divisão de dados/tarefas entre threads (work-sharing)

– for (loop)

– section

– single

– task

Sincronização

– barrier

– master

– critical

– atomic

– ordered

(29)

Exemplo de Diretiva Parallel : Hello World

#include <stdio.h>

#include <stdlib.h> #include <omp.h> int main(){

int nthreads, tid;

#pragma omp parallel private(nthreads, tid) {

tid = omp_get_thread_num();

printf("Hello World da thread = %d\n", tid); if (tid == 0){

nthreads = omp_get_num_threads();

printf(“Quantidade de threads = %d\n", nthreads); }

} }

Cláusula que indica que as threads terão suas próprias cópias de variáveis nthreads e tid

Delimitação da região paralela

(30)

Cláusulas OpenMP

Diretivas podem vir seguidas de cláusulas

Cláusulas são usadas para especificar informações

adicionais à diretiva utilizada

Pode-se utilizar várias cláusulas em uma mesma

instância de diretiva

Certas cláusulas só podem ser utilizadas para algumas

diretivas específicas

(31)
(32)

Exemplo de Diretiva Parallel : Hello World

#include <stdio.h>

#include <stdlib.h> #include <omp.h> int main(){

int nthreads, tid;

#pragma omp parallel num_threads(5) private(nthreads, tid) {

tid = omp_get_thread_num();

printf("Hello World da thread = %d\n", tid); if (tid == 0){

nthreads = omp_get_num_threads();

printf(“Quantidade de threads = %d\n", nthreads); }

} }

Cláusula que indica que as threads terão suas próprias cópias de variáveis nthreads e tid

Cláusula que indica a quantidade de threads desejada

(33)

Diretivas de Work-Sharing

O processamento é distribuído entre as threads, ou seja o

trabalho é realizado conjuntamente pelas threads

Estas diretivas devem estar presentes dentro de regiões

paralelas

Não podem criar novas threads dentro das áreas

limitadas por estas diretivas

(34)

Tipos de Diretivas de OpenMP

Básica de Paralelismo

– parallel

Divisão de dados/tarefas entre threads (work-sharing)

– for (loop)

– section

– single

– task

Sincronização

– barrier

– master

– critical

– atomic

– ordered

(35)

Diretiva for

Serve para distribuir as iterações de um laço entre as

threads

– Naturalmente deve existir um laço logo após este tipo de

diretiva

– O laço deve ser do tipo for

A diretiva for não deve vir seguida de chaves { }

Assim como outras diretivas, eles permitem o uso de

cláusulas

Forma Geral:

(36)

Exemplo de Diretiva for : Soma Matriz

#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[2][2] = {{1,2},{3,4}}; int B[2][2] = {{5,6},{7,8}}; int C[2][2]; int i,j,tid;

#pragma omp parallel private(j,tid) {

#pragma omp for

for(i=0; i < 2; i++) { for(j=0; j < 2; j++){

tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } }

Variável de controle do loop (i) é privada por padrão, haverá uma thread alocada para calcular o valor de cada linha

Importante para que uma thread não interfira no valor de j de outra thread

(37)
(38)

Combinando parallel e for

São equivalentes

As duas diretivas podem ser combinadas na mesma linha

– Naturalmente deve existir um laço logo após este tipo de

diretiva

(39)

Exemplo de parallel e for : Soma Matriz

#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[2][2] = {{1,2},{3,4}}; int B[2][2] = {{5,6},{7,8}}; int C[2][2]; int i,j,tid;

#pragma omp parallel for private(j,tid) for(i=0; i < 2; i++) {

for(j=0; j < 2; j++){

tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \

C[%d][%d]=%d\n",tid,i,j,C[i][j]); }

} }

Não precisa de chaves para delimitar área

(40)

Tipos de Diretivas de OpenMP

Básica de Paralelismo

– parallel

Divisão de dados/tarefas entre threads (work-sharing)

– for (loop)

– section

– single

– task

Sincronização

– barrier

– master

– critical

– atomic

– ordered

(41)

Diretivas sections e section

A diretiva sections serve para listar blocos de código que

serão processados por threads separadas

– Cada bloco deve vir precedido da diretiva section

As diretivas sections e section são delimitadas por chaves

{ }

Caso uma thread seja muito rápida, ela pode executar

mais de uma section

– Ou ainda, se o número de threads definido pelo programador

for menor do que o número de section

(42)

Formas Gerais de sections e section

#pragma omp

sections

[claúsula[cláusula]]

{

#pragma omp

section

{código}

#pragma omp

section

{código}

}

(43)

Exemplo de sections : Quantidade de Pares

#include <stdio.h> #include <stdlib.h> #include <omp.h> #define N 1000 int main(){ int matriz[N];

int i,tid, valorInicial = 1, contaPar = 0; //Inicializando a matriz

for (i = 0; i < N; i++) {

matriz[i] = valorInicial; valorInicial += 3;

(44)

Exemplo de sections : Quantidade de Pares

#pragma omp parallel private(i,tid) num_threads(2) {

#pragma omp sections reduction(+:contaPar) {

#pragma omp section {

for(i = 0; i < N/2; i++) if (matriz[i] % 2 == 0) contaPar++;

}

#pragma omp section { for(i = N/2; i < N; i++) if (matriz[i] % 2 == 0) contaPar++; } } }

printf("\nQuantidade de pares = %d\n",contaPar); }

Cláusula que garante que os valores das cópias locais de contaPar sejam depois combinadas em um único valor global

(45)

Sincronização

Em um ambiente onde o processamento é paralelo, muitas

vezes precisamos de sincronização para garantir a

corretude da execução do código

– Evitar “race conditions”

Algumas situações onde sincronização se faz necessário:

– Acesso a variáveis compartilhadas, onde toda thread deve

enxergar o valor atualizado da variável

– Execução ordenada de trechos de código

OpenMP possui várias diretivas que possibilitam a

sincronização entre threads diferentes

Devem ser usadas com cautela, pois sincronização é

custosa e afeta o desempenho

(46)

Formas de Sincronização

As duas formas mais comuns de sincronização são:

Barreira (Barrier)

: cada thread

espera em um ponto de

execução (barreira) até que as

outras threads cheguem

Exclusão Mútua

: Define um

bloco de código (região crítica)

onde apenas uma thread pode

executar em um instante de

tempo

(47)

Tipos de Diretivas de OpenMP

Básica de Paralelismo

– parallel

Divisão de dados/tarefas entre threads (work-sharing)

– for (loop)

– section

– single

– task

Sincronização

– barrier

– master

– critical

– atomic

– ordered

(48)

Quando Usar Barreiras (Barriers)

Suponham que tivéssemos estes dois

trechos de código e que o loop estivesse

sendo executado em paralelo sobre i

if (num_thread % 2 == 0) {

fator1 = fator1 + 1;

} else {

fator2 = fator2 + 1;

}

for (i = 0; i < N; i++)

a[i] = i * fator2 * fator1;

(49)

Usando Barreiras (Barriers)

for (i = 0; i < N; i++)

a[i] = a[i]* fator1 * fator2

Espera!

Todas as threads precisam saber o valor de

fator, antes de proceder ao loop

Barreira

Cada thread espera no ponto da barreira e só

continua quando todas as threads tiverem

atingido este ponto

if (num_thread % 2 == 0) {

fator1 = fator1 + 1;

} else {

fator2 = fator2 + 1;

}

(50)

Diretiva barrier

Serve para colocar uma barreira de sincronização

– Cada thread deve esperar as outras quando encontrada esta

diretiva

Forma Geral:

(51)

Exemplo de barrier

#include <omp.h>

#define N 20 int main(){

int i, num_thread,a[N], fator1 =0,fator2=0;

#pragma omp parallel private(num_thread)shared(fator1,fator2) { num_thread = omp_get_thread_num(); if (num_thread % 2 == 0) fator1++; else fator2++;

#pragma omp barrier #pragma omp for

for(i=0; i < N; i++) {

a[i] = i * fator1 * fator2; printf("a[%d] = %d ",i,a[i]); }

}

Cláusula opcional que declara que fator1 e fator2 são

compartilhadas entre threads

Todas as threads devem chegar neste ponto para proceder ao resto do código

(52)

Barreiras Implícitas

Nem sempre precisamos da diretiva

barrier para

especificar uma barreira

Em várias situações há barreiras implícitas consideradas

pelo compilador:

– Fim de regiões paralelas

– Construções work-sharing como

for

possuem barreiras

implícitas ao final do loop, excetuando-se quando se coloca a

cláusula nowait na diretiva

#pragma omp

for

for (i = 0; i < N; i++) {

d[i] = a[i] + b[i];

}

Barreira implícita

(53)

Tipos de Diretivas de OpenMP

Básica de Paralelismo

– parallel

Divisão de dados/tarefas entre threads (work-sharing)

– for (loop)

– section

– single

– task

Sincronização

– barrier

– master

– critical

– atomic

– ordered

(54)

Diretiva critical

Serve para colocar delimitar uma região crítica

– Só uma thread por vez pode entrar na região crítica

– As demais esperam para entrar (barreira implícita)

Bastante útil para atualização de variáveis compartilhadas

– Garante a consistência de dados

Forma Geral:

#pragma omp

critical

[name]

{código}

name

é opcional, mas regiões críticas com o mesmo nome

são tratados como a mesma região crítica

(55)

Exemplo de critical

#include <omp.h>

int main() {

int fator1 =0; int tid;

#pragma omp parallel num_threads(5) private(tid) shared(fator1) {

tid = omp_get_thread_num(); #pragma omp critical

{

fator1++;

printf("\nThread %d executando\n",tid); printf("\nValor de fator1 = %d\n",fator1); } } return 0; } Variável compartilhada entre threads Garante a consistência na atualização do dado e fluxo correto de execução do código

(56)

Saída do Programa SEM critical

Não gera o resultado esperado

(57)
(58)

Tipos de Diretivas de OpenMP

Básica de Paralelismo

– parallel

Divisão de dados/tarefas entre threads (work-sharing)

– for (loop)

– section

– single

– task

Sincronização

– barrier

– master

– critical

– atomic

– ordered

(59)

Diretivas master e single

Diretiva single especifica que somente uma thread executa

o que está delimitada pela diretiva

– Escolha da thread é aleatória

– Restante das threads esperam enquanto a thread termina a

execução do código delimitado (barreira implícita), exceto

quando há uma cláusula nowait

Diretiva master especifica que somente a thread master

executa o código delimitado pela diretiva

– Outras threads podem continuar a executar o restante do

código (não há barreira implícita)

Formas Gerais:

#pragma omp

master

{código}

#pragma omp

single[cláusulas]

(60)

Comportamento de single

Fonte:

(61)

Funções da API de OpenMP

OpenMP oferece várias funções que

podem ser chamadas

– Para controlar e ver o status do

ambiente de execução paralela

– Pode-se configurar a quantidade de

threads dinamicamente além de

sincronizar acesso a regiões críticas

Estas funções tem precedência sobre

(62)

Sumário das Funções na API

Nome Funcionalidade

omp_set_num_threads Estabelece a quantidade de threads

omp_get_num_threads Retorna a quantidade de threads

omp_get_thread_num Retorna o id da thread

omp_get_num_procs Retorna a quantidade de processadores

omp_get_wtime Retorna o tempo do “wall clock”

omp_get_wtick Retorna o número de segundos entre os

ticks do clock

omp_init_lock Associa um lock (semáforo) com uma

variável

omp_destroy_lock Desassocia um lock de uma variável

omp_set_lock Adquire o semáforo, senão bloqueia

omp_unset_lock Libera o semáforo

(63)

Exemplo de Utilização das Funções da API

#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[4][2] = {{1,2},{3,4},{5,6},{7,8}}; int B[4][2] = {{5,6},{7,8},{9,10},{11,12}}; int C[4][2]; int i,j,tid; omp_set_num_threads(2);

#pragma omp parallel for private(j) for(i=0; i < 4; i++) {

for(j=0; j < 2; j++){

tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Estabelece 2 threads Retorna o id da thread

(64)
(65)

Precedência de Diretivas

#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[4][2] = {{1,2},{3,4},{5,6},{7,8}}; int B[4][2] = {{5,6},{7,8},{9,10},{11,12}}; int C[4][2]; int i,j,tid; omp_set_num_threads(2);

#pragma omp parallel for private(j) num_threads(4) for(i=0; i < 4; i++) {

for(j=0; j < 2; j++){

tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Estabelece 2 threads Neste caso, a precedência é da

(66)
(67)

Funções de Lock

Locks oferecem uma flexibilidade

maior em relação a regiões críticas

(diretiva

critical)

– Permitem que as threads que não

adquiriram o lock façam outras

coisas enquanto esperam

Variáveis de lock devem ser

manipuladas através das funções

apropriadas de OpenMP

Locks devem ser inicializados

– Comportamento indefinido caso

não sejam inicializados

(68)

Exemplo de Utilização de Funções de Locks

#include <omp.h> #include <windows.h> int main(){ int tid; omp_lock_t lck; omp_init_lock(&lck); omp_set_num_threads(3);

#pragma omp parallel shared(lck) private(tid) {

tid = omp_get_thread_num(); while (!omp_test_lock(&lck)) {

printf("\nThread %d esperando lock \n",tid); Sleep(3);

}

printf("\nThread %d adquiriu lock\n",tid); Sleep(5);

printf("\nThread %d liberou lock\n",tid); omp_unset_lock(&lck);

} }

Lock deve ser inicializado

Testa e adquire o lock

Enquanto não adquire, faz outra coisa

(69)

Saída do Programa com Locks

Thread 0 adquiriu o lock, e threads 1 e 2 fazem outra coisa enquanto esperam

(70)

Algumas Variáveis de Ambiente OpenMP

Nome Funcionalidade

OMP_NUM_THREADS Estabelece a quantidade de threads

OMP_DYNAMIC Habilita/Desabilita se a quantidade de

threads pode ser ajustada dinamicamente pelo ambiente de execução

OMP_NESTED Habilita/Desabilita o aninhamento de

paralelismo

Existem muitas variáveis de ambiente que podem modificar

o ambiente de execução

Quando há uma cláusula de diretiva ou função da API que

tem a mesma funcionalidade, estes tem maior precedência

sobre as variáveis de ambiente

– Cláusulas > Funções > Variáveis de ambiente

Alguns exemplos de variáveis de ambiente:

Referências

Documentos relacionados

5.21 - Termo de Referência: É o instrumento orientador, elaborado pelo órgão ambiental com a participação do empreendedor, que tem como finalidade estabelecer as diretrizes para a

Serão realizadas também aulas remotas com turmas pequenas (decidir com a turma) para discussão entre o professor e os alunos referentes a cada ítem da ementa de laboratórios

Essas operações, com e sem risco de crédito, são contabilizadas em conta do ativo “Outros créditos de operações com planos de assistência à saúde”

A decisão recorrida não acolheu a pretensão orientada na inicial ao entendimento de que o grupo familiar possui renda mensal per capita superior a ¼ do salário-mínimo.

3 - Quando não possa reparar-se convenientemente o caixão deteriorado, encerrar-se-à o mesmo noutro caixão de zinco ou será removido para sepultura à escolha

O PROGRAMA DE PÓS-GRADUAÇÃO EM FISIOPATOLOGIA E CIÊNCIAS CIRÚRGICAS, DA UNIVERSIDADE DO ESTADO DO RIO DE JANEIRO - UERJ torna público o presente Edital,

Podemos considerar que foi possível realizar um progra- ma de fortalecimento isotônico para os músculos responsáveis pela preensão palmar e que para esse grupo de voluntárias e

– É mais rápido terminar um thread que um processo – É mais rápido chavear entre threads de um