You are on page 1of 20

Uma introdu c ao a Pthreads em linguagem C

Guilherme OConnor de Lungarzo Junho de 2003

Sum ario
1 Aloca c ao de mem oria 2 O que e uma thread? 3 O que s ao PThreads? 4 Criando PThreads 5 Passando Argumentos para PThreads 6 Juntando e controlando PThreads 7 O uso de mutex 8 O uso de vari aveis de condi c ao 2 4 6 6 8 9 13 16

Nota
A primeira se c ao deste documento trata de aloca c ao de mem oria para programas escritos em C, o leitor que conhe ca bem este assunto, pode pular diretamente para a segunda se c ao, sem preju zos. Este texto e distribu do junto com o arquivo programas.tgz que cont em todos os c odigos fonte dos programas exibidos em exemplos neste texto.

Aloca c ao de mem oria

Quando executamos um programa que foi escrito em C, o sistema operacional carrega na mem oria o c odigo execut avel do programa em uma area da mem oria. A esta area damos o nome de area de texto. Em seguida e alocada mem oria para todas as vari aveis globais, e ent ao uma area para as vari aveis que s ao declaradas pela fun c ao principal do programa, fun c ao main. Esta e chamada de area de dados. Depois, inicia-se a execu c ao do programa, isto e feito porque o sistema operacional guarda um ponteiro para a area de texto, de onde retira o c odigo execut avel e o envia para o processador, que se encarrega da execu c ao. O programa tamb em possui uma pilha que e chamada de pilha de execu c ao, que e onde o sistema operacional guarda as informa c oes a respeito do estado do programa (valores dos registradores, ponteiros para instru c oes, etc.) quando a fun c ao suspende sua execu c ao. O programa pode suspender sua execu c ao por diversos motivos, o mais obvio e que em um sistema multitarefa preemptivo o sistema operacional distribui fatias de tempo para cada processo, ou seja, cada inst ancia de um programa em execu c ao. Quando o tempo ao qual o processo tinha direito se esgota, o sistema operacional, guarda o seu estado na pilha e o suspende, colocando outro processo em execu c ao. Isto e feito exatamente do modo inverso, os valores na pilha de execu c ao deste processo s ao restaurados e o programa e posto para rodar. Outra maneira de uma fun c ao ter sua execu c ao suspensa e quando ela faz uma chamada a outra fun c ao, o estado dela e guardado na pilha, mem oria e alocada para a area de dados da fun c ao, o sistema operacional guarda um ponteiro para o lugar, na area de texto, onde est a o c odigo execut avel do programa e a execu c ao come ca. Analogamente ao que ocorre com o main, que anal de contas tamb em e uma fun c ao. Sempre que uma fun c ao chama outra os dados desta s ao armazenados na pilha, em espera, e uma nova linha de execu c ao e criada. Observe por exemplo o seguinte programa:
fatorial.c
1 2 3 4 5 6

# include <stdio.h> int fatorial(int a){ int fat=1; if(a>1) fat = a * fatorial(a-1);

7 8 9 10 11 12 13 14 15 16 17

else fat = 1; return fat; } int main(){ int fat,a; scanf("%d",&a); fat = fatorial(a); printf("%d",fat); }

No in cio da execu c ao o c odigo compilado do programa e colocado na area de texto, depois, a mem oria correspondente ` as vari aveis fat e a e separada, criando a area de dados da fun c ao main e o processo come ca a ser executado. Ao chegar ` a atribui c ao na linha 13 o valor de fatorial(a) deve ser avaliado antes de ser atribu do a fat. Isto ocorre suspendendo-se a execu c ao da main, guardando seu contexto, e repetindo o procedimento para a fun c ao fatorial. Cria-se a area de dados para guardar a vari avel fat, recupera-se o c odigo execut avel na area de texto e executa-se a fun c ao, que, eventualmente, vai ser interrompida por outra inst ancia da fun c ao fatorial que ap os sua execu c ao vai permitir a retirada da fun c ao no topo da pilha e assim sucessivamente. O programa acima, e tipicamente seq uencial, j a que cada vez que uma chamada de fun c ao e feita o programa depende deste valor para continuar sua execu c ao. Mas tome por exemplo o seguinte programa1 :
hipotenusa.c
1 2 3 4 5 6 7 8 9 10 11

#include<stdio.h> #include<math.h> int quadrado(int a){ int quad; quad = a*a; return quad; } int main(){ int hip, hip_quad; O leitor observar a que os programas aqui exibidos levam uma s erie de etapas desnecess arias, todo o c alculo da hipotenusa poderia ter sido feito em uma linha, por em, motivos did aticos justicam esta constru c ao.
1

12 13 14 15 16 17 18 19 20

int cateto1,cateto1_quad; int cateto2, cateto2_quad; scanf("%d %d",&cateto1,&cateto2); cateto1_quad=quadrado(cateto1); cateto2_quad=quadrado(cateto2); hip_quad = cateto1_quad + cateto2_quad; hip = sqrt(hip_quad); printf("%d\n",hip); }

Quando a primeira chamada ` a fun c ao quadrado2 e executada na linha 15, o seu resultado s o ser a necess ario na linha 17. Isto quer dizer que n ao haveria necessidade de interromper a execu c ao da linha 16, enquanto a fun c ao executa embora seja isto que acontece.

O que e uma thread?

Em um programa em C, poder amos executar as chamadas a fun c oes concorrentemente, se pud essemos conseguir que o sistema operacional criasse uma nova linha de execu c ao, de certa maneira um novo processo. Isto e poss vel nos sistemas operacionais modernos atrav es do suporte a threads, uma thread e um processo que cont em: PID (Process ID); pilha de execu c ao; registradores; propriedades de escalonamento Como a thread e a execu c ao de uma fun c ao, ela precisa ter acesso ao c odigo execut avel na area de texto, bem como ` as vari aveis globais. Para conseguir isto, o sistema operacional cria a thread como um processo que mant em os mesmos ponteiros para a area de dados globais, e um ponteiro para o lugar, dentro da area de texto, onde est a o c odigo da fun c ao.
N ao se esque ca que para compilar este programa com o gcc e necess ario passar o argumento -lm
2

Al em disto, para que a thread possa receber par ametros ela mant em um u nico ponteiro para um bloco cont guo de mem oria que cont em uma struct com todos seus argumentos. Em linguagem C, um ponteiro guarda um endere co de mem oria e o formato dos dados armazenados em tal ponteiro, por exemplo, quando fazemos,
1 2 3

int a; int *ponteiro; ponteiro = &a;

estamos n ao s o armazenando o lugar onde a vari avel a est a sendo guardada, mas tamb em quantos bytes ocupa, podendo assim usar a constru c ao *ponteiro; de maneira id entica ` a vari avel a. Existe, no entanto, uma maneira de se ter um ponteiro n ao formatado, ou seja, uma vari avel que guarde pura e simplesmente um endere co da mem oria, sem ter nenhuma informa c ao a respeito do formato dos dados ali guardados. Um ponteiro deste tipo e declarado como
1

void *ponteiro

Embora possa parecer estranho a primeira vista, ele e um velho conhecido de qualquer programador C. A fun c ao malloc e uma fun c ao que retorna meramente um endere co de mem oria. O in cio de um bloco cont guo de mem oria, n ao formatado, que foi reservado para a fun c ao que a chamou. s E o lembrar que a fun c ao malloc, recebe como u nico argumento um n umero inteiro. O fato de, freq uentemente, usarmos a fun c ao sizeof para determinar este n umero n ao altera o fato de que a fun c ao malloc n ao recebe nenhuma informa c ao a respeito dos dados que ser ao armazenados l a. Outro esclarecimento que se faz necess ario e que em C, o nome de uma fun c ao representa o endere co de mem oria, dentro da area de texto, no qual o c odigo execut avel referente a ela est a localizado. Assim, a thread pode receber o ponteiro para sua area de texto (que e um subconjunto da area de texto do programa) a partir do nome da fun c ao. Em sistemas operacionais modernos, nem todo o c odigo execut avel e carregado de uma vez na area de texto, no entanto o sistema se encarrega destes detalhes e fornece um endere camento que e v alido para ns de uso do programa.

O que s ao PThreads?

Pthreads e um apelido para Posix Threads. Posix, Portable Operating System, Interface for Unix, e um padr ao, denido pela IEEE (Institute of Electrical and Electronics Engineers) e pela ISO (International Organization for Standartization), como o pr oprio nome diz e um padr ao que dene como, no mundo do *nix os programas devem ser interfaceados, visando portabilidade. Ocorre que inicialmente, cada fabricante de hardware tinha seu pr oprio padr ao de threads, o que fazia com que programas multithreaded (ou seja, programas usando threads) fossem muito pouco port aveis de plataforma para plataforma.

Criando PThreads

Fazer uma chamada a uma pthread n ao e muito diferente de fazer uma chamada a uma fun c ao. De fato, um programa Hello World3 pode ser feito da seguinte maneira.
hellosequencial.c
1 2 3 4 5 6 7 8 9

#include <stdio.h> void hello(){ printf("Hello World\n"); } int main(){ hello(); } hellothreaded.c #include <stdio.h> #include <pthread.h> void *hello(){ printf("Hello World\n"); } int main(){ pthread_t id; Como (quase) todo mundo sabe, um Hello World e um programa (normalmente o primeiro programa feito dentro de um paradigma), e tudo o que ele faz e dizer Hello World com pequenas varia c oes
3

1 2 3 4 5 6 7 8 9

10 11 12

pthread_create(&id , NULL , hello , NULL); }

Para compilar e executar este programa com o gcc, voc e deve fornecer os comandos
gcc hellothread.c -o hellothread -lpthread ./hellothread

Os quatro par ametros para a fun c ao pthread create s ao: 1. o endere co do identicador da thread; 2. o endere co dos atributos; 3. o endere co do c odigo a executar (passado, como vimos, atrav es do nome da fun c ao); 4. o endere co da estrutura de argumentos. No entanto, apenas o primeiro e terceiro argumentos s ao obrigat orios. O primeiro, porque a fun c ao vai tentar escrever nele, caso NULL seja passado, haver a um acesso inv alido de mem oria. O terceiro, porque ela tentar a executar o c odigo no endere co passado. Se o endere co for inv alido o mesmo problema ocorrer a. E importante levar em conta que a fun c ao pthread create retorna um valor inteiro que e o c odigo de erro encontrado na cria c ao da thread, ou zero, caso contr ario. Tamb em e importante levar em conta que as threads s o existem enquanto a thread principal existir, a menos que a fun c ao pthread exit seja usada. Esta fun c ao leva um argumento que e um ponteiro para o valor de status da thread. Convenciona-se que um processo que nalizou sua execu c ao adequadamente deve retornar 0, enquanto que quando um erro ocorre, um valor diferente de 0 deve ser retornado.
hellothreadedcorreto.c
1 2 3 4 5

#include <stdio.h> #include <pthread.h> void *hello(){ printf("Hello World\n");

6 7 8 9 10 11 12 13 14 15 16

pthread_exit(NULL); } int main(){ pthread_t id; int status; status = pthread_create(&id , NULL , hello , NULL); if(status!=0) exit(-1); pthread_exit(NULL); }

Passando Argumentos para PThreads

Podemos fazer uma varia c ao do Hello World com um la co que execute um n umero arbitr ario de threads, neste caso seria mais interessante que cada thread imprimisse algo como: Eu sou a thread i de n Note que para saber o valor de n e muito f acil, basta a vari avel ser global, j a o mesmo n ao pode ser feito com a vari avel i porque ela est a constantemente sendo alterada pela thread executando o main. Assim quando uma determinada thread for criada, digamos com i = 3, ela pode n ao ler imediatamente o valor de i, mas faz e-lo um pouco mais tarde quando o main j a a alterou. O i deve ser passado como par ametro. Para fazer isto, basta passar para a thread o endere co do local onde o valor de i foi armazenado, para tal, deve existir um local de armazenamento para estes valores onde eles n ao mudem. Como por exemplo um vetor de argumentos. Onde cada argumento e um struct.
argumentos.c
1 2 3 4 5 6 7 8 9 10 11 12 13

#include<stdio.h> #include<pthread.h> #define MAX_THREADS 100 typedef struct _ARGS{ int i; }ARGS; int n; void *hello(void *p){ int i; i = ((ARGS *)p)->i;

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

printf("Eu sou a thread %d de %d\n", i, n); pthread_exit(NULL); } int main(){ int i; ARGS args[MAX_THREADS]; int status; pthread_t id[MAX_THREADS]; printf("Quantas Threads? "); scanf("%d",&n); for(i=0 ; i<n ; i++){ args[i].i = i; status = pthread_create(&id[i],NULL, hello,(void *)&args[i]); if(status) exit(-1); } pthread_exit(NULL); }

Note a necessidade de se fazer typecasting nas linhas 13 e 30. Na linha 30 a fun c ao pthread create recebe um void* por isso o formato deve ser jogado fora. Na linha 13 o formato da struct ARGS deve ser recuperado para poder acessar a vari avel i a partir do endere co de mem oria fornecido.

Juntando e controlando PThreads

Nem sempre e interessante que cada thread siga o seu caminho independentemente at e o m. Muitas vezes os resultados obtidos por cada thread precisam ser juntados para fazer processamento seq uencial, para outra rodada de processamento paralelo ou meramente para imprimir os resultados em uma ordem espec ca. Para isto existe a diretiva pthread join que se encarrega de esperar o t ermino da execu c ao de uma thread, antes de continuar o processamento. A fun c ao pthread join recebe dois argumentos, o identicador de uma thread (declarado como pthread t) e um ponteiro para ponteiro desformatado (void **ret) para o valor de retorno da thread. A fun c ao pthread join, analoga9

mente a pthread create retorna um valor zero se a thread foi adequadamente juntada4 e um valor diferente de zero caso tenha ocorrido um erro. No seguinte exemplo, colocamos em execu c ao N THREADS threads. Cada uma vai somar N n umeros aleat orios entre 0 e MAX. A thread principal, vai aguardar os resultados parciais, somar entre si e exibir o resultado. Cada thread vai guardar o seu resultado parcial em uma posi c ao do vetor soma parcial[N THREADS].
somaaleat.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

#include <stdio.h> #include <stdlibh> #include <pthread.h> #define N_THREADS 10 #define N 100000 #define MAX 10 typedef struct _ARGS{ int id; }ARGS; int soma_parcial[N_THREADS]; void *realiza_soma(void *p){ int resultado=0, i; int meu_id = ((ARGS *)p)->id; /* soma N numeros aleatorios entre 0 e MAX */ for(i=0 ; i<N ; i++) resultado += rand()%MAX; /* armazena a soma parcial */ soma_parcial[meu_id] = resultado; pthread_exit((void *)0); } int main(){ int i, rc, status, somatotal=0; pthread_t id[N_THREADS]; ARGS args[N_THREADS]; /* cria cada uma das threads */ Embora a palavra seja feia ela existe e est a correta. Tecnicamente o termo em ingl es e joined=juntada.
4

10

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

printf("\nCriando threads:"); for(i=0 ; i< N_THREADS ; i++){ args[i].id = i; rc = pthread_create(&id[i], NULL, realiza_soma, &args[i]); if (rc){ printf("\nErro %d na cria c~ ao\n",rc); exit(-1); } printf(" [%d]",i); } /* junta cada uma das threads */ printf("\nJuntando threads:"); for(i=0 ; i< N_THREADS ; i++){ rc = pthread_join(id[i],(void **)&status); if (rc){ printf("\nErro %d na jun c~ ao\n",rc); exit(-1); } printf(" [%d->%d]",i,status); } /* conclui a soma */ printf("\nSomando parcelas:"); for(i=0 ; i< N_THREADS ; i++){ printf(" [%d]", soma_parcial[i]); somatotal += soma_parcial[i]; } /* imprime o resultado */ printf("\nSoma total: %d\n",somatotal); pthread_exit(NULL); }

O objetivo do la co entre as linhas 48 e 55 e esperar a execu c ao de todas as threads antes de fazer a soma nal. Isto e desejado, obviamente, para garantir que cada thread tenha colocado sua soma parcial no vetor soma parcial antes de realizar o procedimento. A linha 54 imprime uma mensagem indicando que a thread i foi juntada e o seu status. Neste programa, por ser muito simples, o status de todas as threads e zero, por isso a thread principal n ao faz mais que exibi-lo, uma 11

thread mais complexa poderia retornar valores diferentes testados pelo main para exibir mensagens de erro ou outras atitudes a crit erio do programador. No entanto, nem sempre e poss vel juntar threads. O segundo argumento passado para pthread create e um atributo que pode ser joinable ou detached. Para que uma thread possa ser juntada ela tem que ser do tipo joinable, que e o default do Posix. Mas, cuidado, isso n ao garante que toda implementa c ao siga essa orienta c ao. Asim, e sempre uma boa id eia explicitar que uma thread e joinable, quando isto e desej avel. Isto faz o c odigo mais port avel e menos propenso a ter problemas. Para fazer isto e necess ario criar uma vari avel do tipo pthread attr t, inicializ a-la com a fun c ao pthread attr init e faz e-la joinable com a fun c ao pthread attr setdetachstate. Finalmente esta vari avel de atributo pode ser passada como par ametro para a fun c ao pthread create. O seguinte c odigo intercalado adequadamente no programa somaaleat.c faria o servi co.
1 2 3 4

pthread_attr_t atributo; pthread_attr_init(&atributo); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&id[i], atributo, realiza_soma, &args[i]);

Quando uma thread e joinable, isto signica que ap os sua execu c ao a mem oria ocupada por ela n ao e liberada de imediato, isto e feito ap os o join, quando a thread principal j a recuperou o valor de status. Por isto mesmo, quando n ao h a necessidade de uma thread ser esperada, e mais conveniente que ela seja criada em estado detached, o que pode ser feito passando-se o par ametro PTHREAD CREATE DETACHED para pthread attr setdetachstate. Existe tamb em uma maneira de transformar uma thread joinable em uma detached. Atrav es de uma chamada ` a fun c ao pthread detach, que recebe como argumento o id da thread e retorna zero caso o desligamento5 tenha sido bem sucedido.
1

status = pthread_detach(id);
5

detaching

12

O uso de mutex

No programa somaaleat.c cada thread escrevia em uma posi c ao diferente do vetor soma parcial, por isso n ao havia problemas de concorr encia, mas imagine o caso onde mais de uma thread precisa acessar a mesma vari avel. Ler a mesma vari avel n ao tem problema, mas quando desejamos alter ala, precisamos levar em conta que isto pode requerer (e normalmente requer) mais de uma instru c ao de m aquina. Vamos assumir, por exemplo, que sejam necess arios os seguintes passos para alterar uma vari avel: 1. Ler o valor da vari avel, 2. Alterar o valor da vari avel localmente, 3. Recolocar o valor da vari avel. Em um sistema preemptivo, como a maioria dos sistemas modernos, um processo pode perder o processador a qualquer momento sem pr evio aviso, portanto, se duas threads est ao tentando alterar a mesma vari avel pode ocorrer a interrup c ao da primeira, gerando inconsist encia de dados. Por exemplo, imagine que uma vari avel x compartilhada, inicialmente valendo 0 v a ser alterada por duas threads A e B, que guardam localmente o valor de x em vari aveis locais a e b, respectivamente. Poderia ocorrer o seguinte.
Thread A Le valor de x Incrementa de 5 Thread B a 0 5 5 5 5 5 b 0 10 10 10 x 0 0 0 0 10 5

Le valor de x Incrementa de 10 Escreve o valor Escreve o valor

No nal do processo, o valor de x e 5, quando deveria ser 10. Isto ocorre porque a altera c ao da vari avel n ao e at omica, ou seja, indivis vel. Para solucionar isto o sistema tem que prover uma maneira de se executar uma s erie de passos sem que outro processo possa interferir, para isto usamos o mutex, do ingl es mutual exclusion. Um mutex e uma vari avel do tipo thread mutex t que tem dois estados: travado e destravado6 . Quando a fun c ao pthread mutex lock, que recebe uma
6

locked e unlocked

13

vari avel mutex como par ametro, e chamada, o valor do mutex e vericado e uma de duas atitudes e tomada: 1. se o mutex est a destravado, ent ao trava ele e continua a execu c ao; 2. se o mutex est a travado, espera ele mudar de estado, trava e continua e execu c ao. Assim se mais de um processo deve acessar a mesma vari avel, podemos proteger a parte do c odigo que n ao deve ser executada simultaneamente, chamada de regi ao cr tica, com um mutex. Assim, o primeiro processo a alcan car o mutex trava ele, e os seguintes, ao atingirem a regi ao cr tica s ao mantidos em espera pela fun c ao pthread mutex lock at e que o valor do mutex mude. Isto s o vai ocorrer quando, ao sair da regi ao cr tica, a tread que obteve o mutex chamar a fun c ao pthread mutex unlock, que se encarrega de destravar o mutex. Apenas a thread que possui o lock pode destrav a-lo. Em seguida, o primeiro processo escalonado ap os o destravamento, trava o mutex e entra na se c ao cr tica, mantendo os outros processos em espera. Antes de ser usado, o mutex deve ser inicializado, isto pode ser feito estaticamente, durante a declara c ao da vari avel, atrav es de um comando como
1

meu_mutex = PTHREAD_MUTEX_INITIALIZER;

ou dinamicamente atrav es da fun c ao pthread mutex init, que recebe dois argumentos, o endere co da vari avel mutex e os atributos. O valor NULL para o segundo atributo signica aceitar os defaults, o que e equivalente a usar o modo est atico. O modo default e inicializar o mutex como destravado. Atrav es do uso de mutex podemos reescrever o programa somaaleat.c de maneira que n ao seja necess ario que a thread principal execute o processo seq uencial de fazer a somat oria das somas parciais. Cada thread pode fazer isto por si s o. Contudo, tome cuidado, embora pare ca que a soma est a sendo feita paralelamente e, portanto, sendo mais eciente do que a seq uencial, repare que como o mutex s o permite a entrada de um processo por vez ` a regi ao cr tica, na pr atica a soma e seq uencial, apenas n ao e ordenada, j a que as threads podem entrar na regi ao cr tica em uma ordem arbitr aria.

14

Outra coisa que pode, em um primeiro momento, passar pela nossa cabe ca, e que as threads podem ser detached, j a que cada uma vai se encarregar de somar sua parcela ao total. Isto e verdade, mas conv em n ao se esquecer que em geral vamos querer que algu em imprima o resultado. Claro que podemos criar uma estrutura para que cada thread descubra se e a u ltima e, caso seja, se encarregue de imprimir o resultado, mas tal cria c ao e uma complica c ao do c odigo e desnecess aria dentro do escopo deste texto. O seguinte programa implementa as modica c oes discutidas, note que este programa implementa tamb em a declara c ao expl cita das threads como joinable. Fica a cargo do leitor implemetar a vers ao detachable, caso deseje.
somamutex.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

#define N_THREADS 10 #define N 100000 #define MAX 10 typedef struct _ARGS{ int id; }ARGS; pthread_mutex_t meu_mutex = PTHREAD_MUTEX_INITIALIZER;; int somatotal=0; void *realiza_soma(void *p){ int resultado=0, i; int meu_id = ((ARGS *)p)->id; /* soma N numeros aleatorios entre 0 e MAX */ for(i=0 ; i<N ; i++) resultado += rand()%MAX; /* armazena a soma parcial */ pthread_mutex_lock(&meu_mutex); somatotal += resultado; pthread_mutex_unlock(&meu_mutex); printf("\nThread %d: parcial %d",meu_id,resultado); pthread_exit((void *)0); } int main(){ int i, rc, status; pthread_t id[N_THREADS];

15

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

pthread_attr_t atributo; ARGS args[N_THREADS]; /* inicializando atributo */ pthread_attr_init(&atributo); pthread_attr_setdetachstate(&atributo, PTHREAD_CREATE_JOINABLE); /* cria cada uma das threads */ printf("\nCriando threads:"); for(i=0 ; i< N_THREADS ; i++){ args[i].id = i; rc = pthread_create(&id[i], &atributo, realiza_soma, &args[i]); if (rc){ printf("\nErro %d na cria c~ ao\n",rc); exit(-1); } printf(" [%d]",i); } /* junta cada uma das threads */ printf("\nJuntando threads..."); for(i=0 ; i< N_THREADS ; i++){ rc = pthread_join(id[i],(void **)&status); if (rc){ printf("\nErro %d na jun c~ ao\n",rc); exit(-1); } } /* imprime o resultado */ printf("\nSoma total: %d\n",somatotal); pthread_exit(NULL); }

O uso de vari aveis de condi c ao

Existe um caso em particular em que desejamos ter exclus ao m utua a uma regi ao cr tica, mas tamb em desejamos que uma determinada condi c ao seja satisfeita, como conseq u encia do processamento de outra thread. Neste caso, c odigo adicional seria necess ario para fazer a verica c ao da condi c ao antes

16

de fazer a chamada a pthread mutex lock. Mais que isso, uma simples verica c ao n ao e suciente, mas uma verica c ao peri odica at e que a condi c ao seja satisfeita. Chamamos este tipo de constru c ao de Busy Waiting e, como o pr oprio nome diz, mant em o processo ocupado fazendo nada. Isto e ruim pois consome recursos computacionais sem trazer nenhum benef cio. E para evitar este desperd cio que s ao denidas as vari aveis de condi c ao. Uma vari avel de condi c ao e equivalente a um recurso pelo qual o processo espera e entra em uma la, como uma la do spooler de impress ao ou similar. Para entrar nesta la, o processo faz uma chamada wait sobre a vari avel e ca esperando sua vez. Em contrapartida, uma outra thread sabe que a primeira thread est a, ou pode estar esperando por este recurso, ent ao, quando considera a condi c ao satisfeita faz uma chamada signal sobre a vari avel, que sinaliza um processo esperando por ela. Ent ao este processo volta ` a ativa. A API de pthreads implementa as fun c oes pthread cond wait e pthread cond signal para cumprirem estes pap eis sobre uma vari avel declarada como pthread cond t. Esta vari avel tem, necessariamente, que trabalhar associada a um mutex. O procedimento b asico para implementar vari aveis de condi c ao e:
Thread Principal Declara mutex Declara cond Inicializa mutex Inicializa cond Cria A e B Thread A Thread B

Realiza Trabalho Trava o mutex Verica condi c ao Chama wait destravando mutex

Realiza Trabalho

Acorda travando mutex Faz trabalho na S.Cr tica Destrava mutex Continua

Satisfaz a condi c ao Sinaliza Destrava Mutex Continua

Veja na tabela como o procedimento ocorre. A thread A entra na se c ao 17

cr tica para vericar sua condi c ao, o que e feito chamando a fun c ao pthread cond wait. Esta fun c ao realiza tr es opera c oes atomicamente: 1. destrava o mutex 2. espera, propriamente, ser sinalizado 3. trava o mutex Por isto e necess ario que ela receba, no seu segundo par ametro, o endere co do mutex que ela deve alterar. Deste modo, olhando a certa dist ancia, tudo se passa como um mutex normal. No entanto olhado mais de perto vemos que e necess ario que o mutex seja destravado e travado dentro da espera, pois isso e o que permite que a outra thread altere a condi c ao pela qual a primeira thread est a esperando. No programa seguinte, prodcons.c, temos um caso trivial de produtor/consumidor. Isto e, temos uma thread que espera por um valor para ser exibido na tela (consumidor). Enquanto isso, uma outra thread e encarregada de colocar este valor na vari avel.
prodcons.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

#include #include #include #include

<stdio.h> <stdlib.h> <time.h> <unistd.h>

int recurso=42; pthread_mutex_t meu_mutex; pthread_cond_t minha_cond; void *produtor(){ /* espera um pouco para permitir * o consumidor iniciar primeiro */ sleep(3); /* executa se c~ ao cr tica */ pthread_mutex_lock(&meu_mutex); recurso = rand(); pthread_cond_signal(&minha_cond); pthread_mutex_unlock(&meu_mutex); pthread_exit(NULL); }

18

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

void *consumidor(){ pthread_mutex_lock(&meu_mutex); pthread_cond_wait(&minha_cond, &meu_mutex); printf("Valor do recurso: %d\n",recurso); pthread_mutex_unlock(&meu_mutex); } int main(){ pthread_t prod_id, cons_id; /* inicializa o gerador de numeros aleatorios */ srand(time(NULL)); /* inicializa mutex e cond */ pthread_mutex_init(&meu_mutex, NULL); pthread_cond_init(&minha_cond, NULL); /* cria threads */ pthread_create(&cons_id, NULL, consumidor, NULL); pthread_create(&prod_id, NULL, produtor, NULL); pthread_exit(NULL); }

Note que o valor inicial do recurso e 42 e note tamb em que na linha 13, o produtor dorme por 3 segundos antes de fazer qualquer coisa. Caso este fosse um mutex normal, o consumidor, em 3 segundos teria tempo de sobra para travar o mutex, ler o valor de recurso, imprimi-lo na tela, destravar o mutex e sair, antes mesmo do produtor acordar, o valor impresso seria 42. No entanto, na linha 26, o consumidor espera um sinal do produtor. Como o produtor s o enviar a este sinal depois de ter atualizado o recurso, garantimos que a linha 27 s o vai ser executada depois que a linha 17 o for e o valor impresso sempre ser a 14. A execu c ao deste programa pode ser resumida assim:

19

Produtor Dorme 3s Acorda Atualiza recurso Sinaliza Destrava RC Termina

Consumidor Entra na RC Espera sinal, destravando RC

Recebe sinal, travando RC Imprime o valor Destrava RC Termina

Sugest ao para o leitor: modique o programa, comentando as linhas 13, 18 e 26, eliminando o sincronismo e a espera, e execute o programa, o valor ca quase imprevis vel, depende de quem travar o mutex primeiro. Digo quase porque na verdade e muito mais prov avel que o consumidor execute primeiro, simplesmente pela ordem em que as threads s ao criadas, mas s o por este fato.

Agradecimentos
Quero agradecer ao professor Alfredo Goldman Vel Lejbman por me dar a oportunidade de dar a palestra ` a qual este documento, em princ pio, se destina, ao professor Marco Dimas Gubitoso por t ao atenciosamente ter respondido ` as in umeras perguntas que eu lhe z no decorrer da composi c ao deste texto e a Iomar Zaia por me ajudar com a Revis ao.

20

You might also like