You are on page 1of 34

Programacao Multithread com a biblioteca POSIX Threads ¸˜

Pedro Arthur Duarte http://pedroarthur.com.br/ http://under-linux.org/blogs/pedroarthurjedi pedroarthur jedi [AT] gmail com
1

1. Introducao ¸˜
´ Nos ultimos anos temos visto uma enxurrada de novos processadores clamando o uso de m´ ltiplos n´ cleos. Eles se aproveitam dos avancos das tecnologias de miniaturizacao u u ¸ ¸˜ de componentes e criam solucoes que s´ eram dispon´veis anteriormente com o uso de ¸˜ o ı diversos processadores e da duplicacao de diversos outros componentes. ¸˜ Esses processadores tiram proveito da paralelizacao da fila de execucao. Ou seja, ¸˜ ¸˜ ´ ao inv´ s de estarem processando uma unica operacao por vez, tais processadores s˜ o e ¸˜ a capaz de dividir as tarefas, cada qual se dedicando a uma operacao por vez. ¸˜ Para tirar proveito dos recursos, as aplicacoes precisam passar por uma pequena ¸˜ modificacao: divis˜ o dos fluxos de execucao. Ou, como mais conhecido, as aplicacoes ¸˜ a ¸˜ ¸˜ precisam se tornar multithreads. Ou seja, devemos estudar e analisar as aplicacoes visando a identificacao de pon¸˜ ¸˜ tos pass´veis de paralelizacao. Um exemplo simples disso seria um browser de Internet. ı ¸˜ As diferentes abas exibem p´ ginas distintas, portanto, o carregamento de cada aba pode a ocorrer de forma independente uma da outra. Apesar de existirem diversos tipos de paralelismo, focaremos aqui no modelo de multiprocessadores com mem´ ria compartilhada. o Aqui n˜ o ser˜ o apresentados os fundamentos do processamento multithread pois a a deixaria o texto enfadonho, principalmente para a maioria do pessoal que n˜ o possui a interesse nem disposicao de estar analisando funcoes matem´ ticas. Mas, para aqueles que ¸˜ ¸˜ a ´ desejam uma abordagem mais voltada a area da pesquisa acadˆ mica, recomendo o livro e ”Introduction to Parallel Computing”escrito por Grama, Karypis, Kumar e Gupta. No decorrer do texto veremos como criar aplicacoes multithreads e como contor¸˜ nar os problemas envolvidos. Os exemplos estar˜ o em C mas no futuro mostrarei como a criar aplicacoes multithread em Python e Java. Todos os exemplos assumem que vocˆ ¸˜ e esteja desenvolvendo em um derivado do Linux.

2. A Biblioteca POSIX Threads
´ A biblioteca pthreads (POSIX Threads) e baseada nas especificacoes POSIX.1 que define ¸˜ um conjunto de tipos, funcoes e macros relativos a criacao e ao controle de diversos fluxos ¸˜ ¸˜ de execucao, chamados daqui para frete de threads. ¸˜ No modelo de processamento multithreads POSIX, as diversas threads residem no mesmo espaco de enderecamento do processo que as criou, ou seja, compartilham das ¸ ¸ mesmas vari´ veis, descritores de arquivo e ainda compartilham o espaco dispon´vel para a ¸ ı alocacao da pilha do processo. Portanto, mesmo que seu processo tenham 600 threads, a ¸˜ alocacao de mem´ ria estar´ dispon´vel como se fosse apenas um processo. ¸˜ o a ı

Para iniciar o uso de funcoes da biblioteca PThreads, o arquivo fonte deve incluir ¸˜ o cabecalho pthreads: ¸ #include <pthread.h> Al´ m disso, todos os comandos de compilacao devem indicar o uso da biblioteca: e ¸˜ $ gcc -o saida -lpthread entrada.c Doutra forma, pode esperar um monte de mensagens nada amig´ veis do GCC. a 2.1. Criando e terminando novas threads O padr˜ o PThreads exige que funcoes que ser˜ o chamadas para a criacao de novas threads a ¸˜ a ¸˜ possuam uma assinatura espec´fica. A saber: ı void* (void *) Ou seja, a funcao a ser executada precisa obrigatoriamente retornar um ponteiro ¸˜ gen´ rico e receber como parˆ metro de entrada um ponteiro gen´ rico. O valor de retorno, e a e como veremos adiante, pode ser recuperado e usado posteriormente. O parˆ metro de a entrada pode ser usado para passar um dado qualquer para a nova thread. Criada a funcao com a assinatura supracitada, podemos ent˜ o usar a funcao int ¸˜ a ¸˜ pthread create(pthread t*, pthread attr*, void*(*)(void *), void *). O primeiro parˆ metro a ´ receber´ o identificador unico da thread; o segundo parˆ metro serve para criar uma thread a a ´ com atributos especiais, os quais veremos mais abaixo; o terceiro parˆ metro e a funcao a a ¸˜ ´ ser executada pela thread; e o quarto parˆ metro e o parˆ metro de entrada da funcao. O vaa a ¸˜ ´ lor de retorno de pthread create e zero caso a nova thread seja criada com sucesso, ou um valor diferente de zero caso haja algum erro. Recomendo executar ”man pthread create”e dar uma breve lida. Analisemos o seguinte exemplo: #include <stdio.h> #include <pthread.h> void* print(void *data){ char *str = (char *) data; while (1) printf("%s ", str); } int main (int argc, char** argv) { int i; pthread_t threads[argc]; for (i = 1 ; i < argc ; i++) pthread_create(&threads[i], NULL, print, (void*) argv[i]); return 0; }

Nesse pequeno programa queremos que ela imprima na tela os parˆ metros passaa dos ao programa, por´ m, ao inv´ s de fazer o trabalho em s´ rie, ser´ criada uma thread e e e a para cada parˆ metro. Para tal, criamos uma pequena funcao chamada print que atende a ¸˜ todos os requisitos do padr˜ o (valor de retorno e parˆ metro do tipo void*). a a ´ Como se pode inferir, pthread t e um tipo de dado definido pelo padr˜ o. Sua a ´ ´ principal funcao e armazenar o identificado unico de cada thread. Esse valor e usado em ¸˜ ´ algumas funcoes que veremos mais a frente. ¸˜ Como n˜ o temos nenhuma necessidade adicional nessas threads, o segundo a parˆ metro foi definido como nulo e ser´ ignorado pela funcao pthread create, que criar´ a a ¸˜ a as threads com os atributos padr˜ es. o Assim que a thread estiver pronto para executar, o sistema dar´ lugar a funcao a ¸˜ print. Essa far´ um cast do quarto parˆ metro para um char*. Esse dever´ ser impresso a a a tantas vezes quanto poss´vel. ı Agora, devemos compilar nosso programa. Assumindo que esse esteja salvo sob o nome threads.c: $ gcc -o threads -lpthread -Wall threads.c Agora, executemos nossa pequena aplicacao com os parˆ metros “under linux org”: ¸˜ a pedroarthur@coruscant:˜/ccc$ ./a.out under linux org pedroarthur@coruscant:˜/ccc$ Putz! N˜ o saiu nada! Mais uma vez: a pedroarthur@coruscant:˜/ccc$ ./a.out under linux org org org org org org org org org org org org org org org org org org org org org org org org org org org org org under under under under under under under u nder under under under under under under under unde r under under org under org under org linux linux l inux linux linux linux linux linux linux linux linu x under org org org org linux org linux linux linux org linux linux linux org linux org linux org linux linux linux org linux linux linux org linux org lin ux org linux org org org linux org linux org linux linux org linux linux linux org linux org linux org pedroarthur@coruscant:˜/ccc$ ´ Viram que comportamento estranho? Pois e, o uso de threads pode causar efeitos colaterais indeterminados a sua aplicacao. As threads n˜ o s˜ o organizadas de maneira ¸˜ a a serial, portanto, n˜ o se deve esperar nada de seu comportamento. a Nesse primero exemplo, a maioria devia estar esperando que ”under”fosse escrito antes de ”linux”, e por sua vez esse fosse escrito antes de ”org”. Por´ m, na primeira e execucao, n˜ o tivemos nada na sa´da. Ou seja, foi dado ao thread principal um maior ¸˜ a ı tempo de execucao. Ent˜ o, esse avancou logo para a funcao de retorno e saiu do programa. ¸˜ a ¸ ¸˜ J´ na segunda execucao, as threads criadas receberam mais atencao que a thread principal a ¸˜ ¸˜

ao inv´ s de finalizar a thread principal. a Como em nosso exemplo anterior o fluxo de execucao das threads n˜ o tem fim. i < argc . Em outras palavras.h> #include <pthread. que ser´ concatenado o p´ s-fixo ”<defunct>”: a o 1000 6780 14. nesse caso.out] <defunct> 0 0 pts/3 Zl+ 07:28 ´ Ou seja. int i.0 0:00 [a. (void*) argv[i]). para o sistema seu processo e um zumbi. ou ¸ talvez nem cheguem a executar! Estejam cientes! Como nota-se pelo evento. que veremos um pouco mais ¸˜ abaixo. Ent˜ o. Ent˜ o. void**). e mais interessante aguardar que as e outras threads finalizem usando a funcao pthread join(pthread t. pthread_exit(NULL).e puderam cada qual escrever algumas vezes na tela. acredito que tenha ficado a d´ vida de como deixar uma thread executando a u ´ mesmo que se deseje finalizar a funcao main.h> void* print(void *data){ char *str = (char *) data. Por´ m. 1 Para finalizar a execucao pressione ctrl+c. fazendo a thread principal terminar sua execucao dessa forma nota-se. Ent˜ o. ¸˜ a vamos atualizar nosso c´ digo para o seguinte: o #include <stdio. ter um processo nesse estado n˜ o e considerado uma boa pr´ tica. /* Restante do c´digo */ o ´ A funcao pthread exit finaliza a thread na qual ela e chamada. ¸˜ . terminar o fluxo de execucao ¸˜ a ¸˜ 1 sem utilizar a diretiva return. a ¸˜ ´ O segundo parˆ metro e um ponteiro gen´ rico que dever´ receber o valor de retorno da a e a thread que est´ sendo aguardada. n˜ o se deve a a esperar nada do comportamento das threads! Umas podem avancar mais que outras.h> #include <stdlib. a ´ a ´ Portanto. print. bastaria fazer : a /* inicio do c´digo /* o for (i = 1 . Apesar de n˜ o ser um grande a problema. O primeiro ¸˜ ´ ´ parˆ metro e o identificador unico da thread que se deseja aguardar o fim da execucao. NULL. as threads s˜ o finalizadas t˜ o logo a funcao main rea a ¸˜ torna. O parˆ metro de ¸˜ a pthread exit deve ser substituido pelo valor que se deseja retornar.0 0. Um m´ todo simples e finalizar a thread em ¸˜ e que a funcao main est´ executando. e ¸˜ mediante consulta na tabela de processos. O valor de retorno pode ser recuperado por outra funcao da biblioteca PThread. i++) pthread_create(&threads[i]. tentando frisar.

void *retval[argc]. Al´ m disso. i < argc . i < argc . (void*) argv[i]).c Rodando nossa aplicacao com os parˆ metro ”the jedi lairs”: ¸˜ a /* Um monte de repetic˜es das palavras "the". for (i = 1 . tivemos que adicionar chamadas e . (int)retval[i]). printf ("\n"). NULL. print. no final do c´ digo a e o o ¸˜ de print. &retval[i]).srandom(time(NULL)). return 0. } Novamente. assuo mindo o nome do arquivo fonte como threads. i < random() . Colocamos tamb´ m. e a funcao espera receber um ponteiro ¸ ¸˜ gen´ rico. Tenham em mente que essa solucao e a ¸˜ n˜ o e port´ vel!!! a ´ a J´ na funcao main. tivemos que fazer um cast na vari´ vel i. adicionamos um vetor de ponteiros gen´ ricos para receber os a ¸˜ e valores de retorna das threads (linha 21). i++) pthread_create(&threads[i]. "jedi" e "lairs" */ ¸o the: 84570 jedi: 54323 lairs: 39598 pedroarthur@slackhlbr:˜/ccc$ A funcao print foi modificada para imprimir um n´ mero randˆ mico de vezes a ¸˜ u o string passada como parˆ metro (linhas 11 e 12). pthread_exit((void*)i). } int main (int argc. for (i = 0 . devemos compilar o c´ digo incluindo a biblioteca pthreads. char** argv) { int i. for (i = 1 . for (i = 1 . a funcao pthread exit para retornar o valor de i ap´ s o fim da execucao do ¸˜ ´ laco (linha 14). pthread_t threads[argc]. i++) printf("%s ". str). i < argc . i++) printf ("%s: %d\n". i++) pthread_join (threads[i].c: pedroarthur@slackhlbr:˜/ccc$ gcc -lpthread -Wall threads. Como o valor de i e um inteiro. argv[i].

pthread_t thread. ap´ s sepao rada. Os atributos de uma thread podem ser determinados no momento da criacao da mesma. Por fim. devemos inicia¸˜ lizar uma vari´ vel de atributos. ¸˜ a seu valor de retorno permance alocando recursos at´ que alguma outra thread recolha-o e em meio a chamada pthread join. Ap´ s criada e ajustados os valores necess´ rios. o mais frequente deles e a separacao ¸˜ da thread.2. /* Definic˜o de alguns comportamentos especiais */ ¸a pthread_create (&thread. os recursos s˜ o dealocados a automaticamente... como seria feito com um processo que retorna. ¸˜ a pthread_attr_t attr. sendo o tipo atributo pthread attr t. Atributos das Threads Os atributos das threads permitem definir determinados comportamentos para essas. */ pthread_attr_init (&attr). adicionamos um pequeno trecho de c´ digo para imprimir quantas vezes o cada um dos parˆ metros da linha de comando foram impressos (linhas 31 e 32). Para definir os atributos no momento da criacao de uma thread. A funcao pthread attr init inicializa a vari´ vel atributo. evitando ¸˜ a assim que um futuro uso descuidado da vari´ vel atributo gere problemas. Quando temos uma thread n˜ o separada. pthread_attr_destroy (&attr). thread_param).1. definindo seus valores ¸˜ a para o padr˜ o. Separacao das Threads ¸˜ ´ Dentre os comportamentos a serem modificados. Em outras palavras.2. se temos as threads A e B.a funcao pthread join para que as mesmas recebessem os c´ digos de retorno (linhas 26 ¸˜ o e 27). a 2. a Uma thread que chame a funcao pthread join ficar´ bloqueada at´ que a thread ¸˜ a e que ela est´ aguardando finalize. Traducao do inglˆ s detach. &thread_f. Uma atualizacao do excerto de c´ digo mostrado anteriormente demonstra ¸˜ o o uso do pthread attr setdetachstate: . 2. ou com funcoes espec´ficas que tem como parˆ metro o ID da a ¸˜ ı a thread a ser manipulada. a vari´ vel atributo precisa a o a a ser passada como segundo argumento para a funcao pthread create para ser corretamente ¸˜ utilizada. ela ficar´ sem executar nenhuma outra instrucao at´ que a ¸˜ e B chame pthread exit(NULL). A funcao pthread attr destroy remove todos os parˆ metro definidos. /* . e A a chama pthread join(B. Contudo. Essas vari´ veis e as funcoes que as manipulam possuem a a ¸˜ o como pr´ fixo pthread attr. Numa thread separada. O c´ digo abaixo mostra e a inicializacao de uma vari´ vel atributo. uma thread n˜ o mais pode ter seu valor de retorno recuperado por uma chamada a a pthread join. com ¸˜ o uso de vari´ vel atributo. &attr. NULL). separar uma thread significa tornar sua execucao ¸˜ e ¸˜ e finalizacao independente das outras threads.

pthread_t thread. pthread_attr_destroy (&attr). */ o o o pthread_detach(pthread_self()). O c´ digo abaixo apresenta o uma atualizacao da funcao main do nosso primeiro exemplo para que o mesmo utilize-se ¸˜ ¸˜ do escopo do sistema: /* includes e func˜o print */ ¸a int main (int argc. ´ Outra maneira de separar uma thread e chamar a funcao pth¸˜ ´ read detach(pthread t*).2. os novos fluxos de execucao podem rodar em dois escopos distintos. Ou seja. .. caso vocˆ tenha um processo com quatro threads e de escopo de sistema e um outro processo com apenas uma thread.. Nesse ponto cabe introduzir a funcao pthread self().. */ pthread_attr_init (&attr). &attr.pthread_attr_t attr.. */ o o o e pthread detach pode ser chamada por qualquer outra thread. as threads ir˜ o competir como um processo comum a pelos recursos do sistema. /* c´digo. fazemos: ¸˜ /* c´digo.2. O comportamento descrito acima pode ser definido apenas durante a criacao ¸˜ da thread usando uma vari´ vel atributo e passando-a como parˆ metro para a a a funcao pthread attr setscope. as threads ir˜ o competir internamente pelo tempo a a de processador dado ao processo que as criou. Escopo de Execucao ¸˜ No PThreads. PTHREAD_CREATE_DETACHED). pthread_attr_setdetachstate (&attr. Portanto. o escopo do sistema. Essa funcao retorna o identificador ¸˜ ¸˜ da thread que a chama. J´ no escopo do processo. c´digo.. Por´ m. &thread_f. Ou seja. pthread_create (&thread. c´digo. Duas macros definem os comportamentes: PTH¸˜ READ SCOPE SYSTEM e PTHREAD SCOPE PROCESS. thread_param). No ¸˜ primeiro deles. /* . c´digo. O primeiro argumento e a thread a qual se deseja separar. caso desejemos em determinado ponto do fluxo de execucao separar uma thread das demais. char** argv) { int i. todas as threads que recebam como atributo de criacao a vari´ vel attr ir´ ¸˜ a a ser criada separada das demais. c´digo. deve-se estar ciente que misturar o fluxo de execucao das diversas threads pode ter efeitos inesperados. ¸˜ 2. o tempo de processamento ser´ dividido como se o sistema operacional estivesse escalonando 5 processo a diferentes..

pthread_attr_t attr. Caso as duas threads a possuam prioridades distintas. } Apesar de n˜ o apresentar uma mudanca brusca no comportamento do programa. RoundRobin. definida pela macro e ı SCHED OTHER. Al´ m do escopo. a First-in First¸˜ out. ı . O mecanismo e simples: a thread de maior prioridade possui maior o precedˆ ncia. A macro que define esse a ´ ı comportamento por parte do escalonador e SCHED FIFO. o conceito tamb´ m e aplic´ vel as threads. PTHREAD_CREATE_DETACHED). pthread_attr_setscope(&attr. qual ser´ a a a ´ pr´ xima a executar. Apesar de ser usada a palavra ”proı ¸˜ cessos”. pthread_attr_init(&attr). na segunda pol´tica. Caso possuam a mesma prioridade. dada duas threads que est˜ o concorrendo a uma mesmo recurso. PTHREAD_SCOPE_SYSTEM). i < argc . e As pol´ticas de escalonamento permitem a definicao de diferentes algoritmos para ı ¸˜ o controle da ordem de execucao das threads. print. for (i = 1 . (void*)argv[i] return 0. existem outros trˆ s atributos que definem as tomadas de decis˜ o e e a de escalonamento das threads. aproveitando-se tamb´ m das prioridades ¸˜ e ı definidas pela funcao pthread attr setschedparam.2. para a execucao do processo. i++) pthread_create(&threads[i]. A primeira pol´tica. Escalonamento Escalonar um processo significa agendar tempo de processamento e outros recursos. a a que chegou primeiro a fila de escalonamento levar´ a melhor. Um dos mais interessantes s˜ o as prioridades das threa ads. a unica funcao que altera o comportamento do escalonador e pthe ¸˜ ¸˜ read attr setscope. bastando apenas fazer uma substituicao e ´ a ¸˜ dos termos. Esse atributo permite definir. as threads de igual prioridade ser˜ o escalonadas segundo o algoritmo de mesmo a nome. pthread_attr_setdetachstate (&attr. Ou seja. a qual define o escopo de execucao de uma thread. leva o escalonador ao comportamento quem chegar primeiro leva. &attr. a de maior valor levar´ a melhor. usada para sinalizar que as threads n˜ o mais precisam de uma pol´tica a ı espec´fica de escalonamento. podendo algumas delas sofrer por preempcoes. A macro utilizada para esse com¸˜ ´ portamento e a SCHED RR. tais como de entrada/sa´da. a ¸ ´ em situacoes onde o processamento e mais necess´ rio essa mudanca se mostra significa¸˜ a ¸ tiva. pthread_t threads[argc]. Os algoritmos de escalonamento s˜ o a base da tomada de decis˜ o de qual tha a read/processo ir´ executar caso hajam mais de uma thread/processo pronto para tal. quando h´ mais de uma thread bloqueada. No a ´ ´ descrito at´ agora. Existe tamb´ m uma terceira pol´tica.3. 2.

Em outras palavras. diversos problemas um o a tanto quanto dif´ceis de diagnosticar.O mais simples de se compreender. essa ¸˜ liberdade do acesso a mem´ ria pode trazer. } . A maioria desses problemas s˜ o ocasionados pela ı a n˜ o atomicidade das operacoes realizadas nos fluxos de processamento. O c´ digo segue abaixo: a a o #include <pthread. Por outro lado. } No. } Pilha.h> #include <stdio. if (nova) { return nova. int qtde. Pilha *pilha. ignora qualquer chamada as ı pthread attr setschedparam e pthread attr setschedpolicy.h> #define TRUE #define FALSE 1 0 #define MAX_THREADS 10 typedef struct node { void *valor. a macro PTHREAD EXPLICIT SCHED define que a nova thread dever´ utilizar os valores presentes a na vari´ vel atributo. define para a ¸ thread que ser´ criada se ela deve herdar os parˆ metros e as pol´ticas de escalonamento a a ı de sua thread criadora ou se deve obedecer aos valores presentes na vari´ vel atributo.3. typedef struct stack { No *no. a ¸˜ Como um exemplo simples podemos usar o acesso concorrente a uma estrutura de dados dinˆ mica (aqui ser´ uma pilha). Sincronizacao ¸˜ ´ O modelo de paralelismo de usado pelas threads e comumente chamado de m´ ltiplos u processadores de mem´ ria compartilhada. Pilha* NovaPilha () { Pilha *nova = (Pilha *) calloc (1. sizeof(Pilha)). toda a regi˜ o de mem´ ria o a o ´ e acess´vel por qualquer thread. A a macro PTHREAD INHERIT SCHED diz a thread rec´ m criada que use os parˆ metros e e a a pol´tica de escalonamento de sua thread criadora. a heranca de escalonamento. Ou seja. a 2. Apesar de trazer vantagens quanto ao acesso dos dados ı por diversas threads pois evita o uso de mecanismo de comunicacao entre processos.h> #include <stdlib. quando m´ utilizados. struct node *proximo.

no = (No *) calloc (1. } void *threadfa (void *data) { long valor. if (p->no) { No *no = p->no. while (TRUE) { . } return NULL. } return FALSE. } int Empilhar (Pilha *p. p->no = no. if (no) { no->proximo = p->no. } No* Desempilhar (Pilha *p) { if (!p) return FALSE. p->qtde++. sizeof(No)). if (!p) return FALSE. no->valor = valor. p->no = no->proximo. return TRUE.return NULL. p->qtde--. return no. void *valor) { No *no.

} } return NULL. } void *threadfb (void *data) { No *no. pthread_join (threads[0]. NULL). while (TRUE) { no = Desempilhar (pilha). "Empilhando %ld\n". NULL. for (i = 0 . (long) no->valor). for ( .valor = random (). threadfa. if (no) { fprintf (stderr. free (no). (void *) valor). em ambientes multithreads o nosso c´ digo apresentar´ no m´nimo trˆ s erros: Segmentation Fault. } Apesar de correto do ponto de vista sequencial. i++) pthread_create (&threads[i]. o n´ cleo julgar´ que a aplicacao est´ u a ¸˜ a fazendo mal uso dos recursos de mem´ ria e a abortar´ . valor). pilha = NovaPilha(). ¸˜ o O terceiro erro n˜ o nos interessa muito. "Desempilhando %ld\n". int i. } int main () { pthread_t threads[MAX_THREADS]. threadfb. fprintf (stderr. Double Free e o a ı e alocacao excessiva de mem´ ria (Aborted). NULL). NULL. Empilhar (pilha. o a . i < MAX_THREADS . i < MAX_THREADS / 2 . NULL). mas vale citar sua causa: caso as decis˜ es a o de escalonamento sejam tais que as fluxos de execucao que tenham como funcao thre¸˜ ¸˜ adfa rodem muito mais vezes que os outros fluxos. } return NULL. int valor. i++) pthread_create (&threads[i].

¸˜ Alguns podem argumentar que diversas verificacoes resolveriam os problemas. Portanto. dado um conjunto de instrucoes que n˜ o e a ´ ¸˜ a utilizam diretivas de sincronizacao e que realizam acessos concorrentes a segmentos de ¸˜ mem´ ria. seria no uso de uma biblioteca de Garbage Collection (GC). ambos os fluxos liberar˜ o o endereco e ¸˜ a a ¸ presente em no. digamos que antes da execucao de cada um desses fluxos. podemos ver claramente que o fluxo de execucao que a ¸˜ n˜ o avancou estar´ trabalhando com um valor de mem´ ria inconsistente. o que ocasionar´ um Double Free e a execucao ser´ finalizada. os n novos valores empilhados o seriam colhidos pelo GC. Ainda tendo alguns poucos valores ema pilhados. threadfa. teriamos um quarto erro: inconsistˆ ncia dos dados.Tamb´ m causados pelas decis˜ es de escalonamento. Entretando. Digamos que j´ tenhamos alguns poucos a valores empilhados. e bem elaborado caso. Agora. vale primeiramente entender a causa dos erros. Agora. Agora. n˜ o existiria a oportunidade o a para um Double Free. todo c´ digo que necessita do uso de mecanismo de ¸˜ ı o sincronizacao para garantir sua integridade. um fluxo de ¸˜ empilhamento seja executado. e Digamos que n fluxos de desempilhamento avancem at´ a quinta linha da funcao Deseme ¸˜ pilhar. mais uma vez desempilhando. e M AX T HREADS/2 fluxos executando a funcao ¸˜ ¸˜ de desempilhamento e liberando os dados. existe um conjunto de decis˜ es de escalonamento que levam o sistema a um o o estado inconsistente. consideremos que dois fluxos. Portanto. executem e sejam interrompidos na quinta linha da funcao Desempilhar. tornando desnecess´ rio o uso de funcoes como o free. Cada um dos valores rec´ m empilhados seriam perdidos e pois na pr´ xima instrucao dos fluxos de desempilhamento o endereco do topo da pilha o ¸˜ ¸ seria ajustado para outro segmento de mem´ ria. denominamos o secoes cr´ticas. a ¸˜ a ´ Um ultimo. os dois primeiros erros poe o dem ser evitados com o uso de mecanismos de controle de concorrˆ ncia.). consideremos que dois fluxos que estejam desempilhamento executem e sejam interrompidos exatamente na quinta linha da funcao Desempilhar (No ¸˜ *no = p->no. digamos que os dois fluxos ¸˜ avancem at´ que a funcao retorne. . Nesse cen´ rio. antes de e e conhecer tais mecanismos. Agora digamos que um desses fluxos volte ao processador e seja executado at´ a nona linha da funcao threadfb (free (no). analisemos um segundo cen´ rio. sua a ¸ a o o ¸˜ pr´ xima instrucao (p->no = no->proximo) acarretaria num Segmentation Fault. Ou seja. Consideremos o programa executando. Pelo c´ digo podemos ver que teremos M AX T HREADS/2 fluxos executando a o funcao de empilhamento. ¸˜ ¸ No cen´ rio descrito acima. threadfb. Em outras palavras. ¸˜ Por´ m. Agora. j´ e matematicamente provado que. Por´ m. Logo. Aos trechos de c´ digos acessados e modificados concorrentemente. Ou seja. depois do retorno da funcao e ¸˜ ¸˜ Desempilhar e da liberacao do endereco presente em no.). Essa bibliotecas permitem ao desenvolvedor uma maior flexibilidade na dealocacao de mem´ ria. a ¸˜ o a ¸˜ pr´ pria biblioteca se encarregaria de liberar os dados.

} else { /* processamento alternativo */ } Estando o funcionamento b´ sico explicado. pthread_mutex_init (&mutex.3. As funcoes de lock. ela sinaliza ao fluxo atrav´ s de seu c´ digo de retorno. /* Nos fluxos que necessitam de sincronizacao */ ¸˜ pthread_mutex_lock (&mutex). Ela testa primeiramente a vari´ vel. mais uma vez abstrato. Para tal comportamento e necess´ rio o uso de e a duas funcoes. NULL). ¸˜ Antes de entrar numa secao cr´tica. e ¸˜ a a u a ´ Para casos onde e prefer´vel fazer outro processamento ao inv´ s de esperar na vari´ vel ı e a de exclus˜ o m´ tua.1. Apartir de agora abreviaremos ”vari´ veis a de exclus˜ o m´ tua”para ”mutex”(do inglˆ s. ¸˜ ¸˜ a No POSIX threads.´ 2. essas vari´ veis garantem que apenas um dos fluxo de proa cessamento tenham acesso a uma secao cr´tica. vamos a um r´ pido e abstrato exemplo de uso de mutexes: o a /* Em um dos fluxos */ pthread_mutex_t mutex. o fluxo corrente ganha passagem. Primeiramente precisamos verificar quais pontos realmente precisam de sincronizacao. Vari´ veis de Exclus˜ o Mutua a a Como sugerido pelo nome. pthread mutex unlock e pthread mutex trylock. Elas realizam seu trabalho usando uma ¸˜ ı ´ t´ cnica de bloqueio com espera ociosa. o tipo das vari´ veis de exclus˜ o m´ tua s˜ o chamados de ptha a u a ¸˜ a read mutex t. unlock e trylock s˜ o chamadas de pthread mutex lock. Esse processo e comumente chamado de ´ seca ¸ ı ”Aquisicao”da vari´ vel. Um exemplo. o fluxo corrente precisa pedir passagem a ¸˜ ı vari´ vel de exclus˜ o m´ tua atrav´ s da funcao lock. /* sec˜o cr´tica */ ¸a ı pthread_mutex_unlock (&mutex). a a ¸˜ Como dito pelo cientista computacional Edsongley. o fluxo corrente permanece blo¸˜ a queado at´ que seja chamada a funcao unlock na vari´ vel de exclus˜ o m´ tua em quest˜ o. num processo chamado de ”liberacao”da vari´ vel. Caso outro fluxo esteja na vez. Antes de utilizar uma a u e ´ mutex e necess´ rio inicializ´ -la com a funcao pthread mutex init. e caso j´ a u ¸˜ a a esteja bloqueada. Para sinalizar a sa´da da secao cr´tica. n˜ o existe documentacao mea ¸˜ lhor que c´ digo fonte. MUTual EXclusion). vamos agora ao nosso c´ digo de a o acesso concorrente a pilha. A maneira mais simples e r´ pida seria sincronizar a chamada das funcoes ¸˜ a ¸˜ . lock e unlock. Caso nenhum outro fluxo esteja nessa a a u e ¸˜ ˜ o cr´tica. o fluxo de processa¸˜ ı ı ¸˜ ı mento deve chamar a funcao unlock. e o a o fluxo entra na secao cr´tica. Caso contr´ rio. de uso do trylock seria: if (!pthread_mutex_trylock (&mutex)) { /* sec˜o cr´tica */ ¸a ı pthread_mutex_unlock (&mutex). existe a funcao trylock.

"Empilhando %ld\n". . "Desempilhando %ld\n". } return NULL. } } return NULL. free (no). pthread_mutex_lock (&mutex). pthread_mutex_unlock (&mutex). fprintf (stderr. (void *) valor). Empilhar (pilha. int valor. pthread_mutex_unlock (&mutex).. */ void *threadfa (void *data) { long valor. (long) no->valor). while (TRUE) { valor = random ().. } int main () { pthread_t threads[MAX_THREADS]. no = Desempilhar (pilha). Ficaria ¸˜ mais ou menos assim: /* . */ #define MAX_THREADS 20 pthread_mutex_t mutex. valor).Empilhar e Desempilhar. /* . while (TRUE) { pthread_mutex_lock (&mutex).. } void *threadfb (void *data) { No *no.. if (no) { fprintf (stderr. bastando apenas inserir uma mutex global e adicionar as chamadas a pthread mutex lock e pthread mutex unlock nas funcoes threadfa e threadfb.

a Portanto. seria mais eficiente o controle de concorrˆ ncia ser inerente a estrutura de e dado Pilha. naive method). } Pilha. for ( . NULL). threadfb. ¸˜ ¸˜ a ¸˜ Trocas de contexto devem ser evitadas ao m´ ximo. i++) pthread_create (&threads[i]. } ´ Essa e conhecida como o m´ todo ”inocente”(do inglˆ s. pthread_join (threads[0]. NULL). NULL). for (i = 0 . i++) pthread_create (&threads[i]. impedimos a chamada da funcao: a eme ¸˜ pilhagem dos argumentos e do endereco de retorno tamb´ m estar´ sendo sincronizado. Imagine uma novo programador entrando na equipe de desenvolvimento e esquecendo que deveria sincronizar as chamadas? Ent˜ o. temos uma alocacao dinˆ mica ocorrendo na a ¸˜ a funcao Empilhar. } . Primeiro. Ou seja. Pilha* NovaPilha () { Pilha *nova = (Pilha *) calloc (1. Alocacao dinˆ mica exige uma troca de contexto para sua execucao. por´ m n˜ o e a mais inteligente (eficiente). NULL).int i. Ela funcie e ona. pilha = NovaPilha(). o ¸ e a ´ que e totalmente desnecess´ rio!. NULL). return nova. Al´ m de mais eficiente. nosso c´ digo ficaria como a o exposto abaixo: typedef struct stack { No *no. pthread_mutex_init (&mutex. sizeof(Pilha)). podemos dizer que ¸˜ e ´ e mais seguro. i < MAX_THREADS / 2 . NULL. e a ´ Analisando-se bem o c´ digo podemos ver que existe um grande desperd´cio de o ı tempo usando-se o m´ todo inocente. Pilha *pilha. a pr´ pria estrutura de dados teria uma mutex e o lock e unlock seriam o feitos pelas funcoes Empilhar e Desempilhar. pthread_mutex_t mutex. i < MAX_THREADS . if (nova) { pthread_mutex_init (&nova->mutex. NULL. Segundo. int qtde. threadfa.

return TRUE. p->no = no. } return FALSE. p->no = no->proximo. no->valor = valor. sizeof(No)). pthread_mutex_unlock (&p->mutex). if (no) { pthread_mutex_lock (&p->mutex). } . no->proximo = p->no.return NULL. pthread_mutex_unlock (&p->mutex). void *valor) { No *no. no = (No *) calloc (1. } No* Desempilhar (Pilha *p) { if (!p) return FALSE. p->qtde++. } int Empilhar (Pilha *p. p->qtde--. if (p->no) { No *no = p->no. if (!p) return FALSE. pthread_mutex_lock (&p->mutex). return no.

. temos uma estrutura de dados do tipo fila ı thread-safe. o ¸˜ ´ Atributos das vari´ veis de exclus˜ o mutua a a Como devem ter notado. */ pthread_mutexattr_destroy(&mutexattr). a funcao pthread mutex init recebe dois parˆ metros... /* Definicao de alguns comportamentos especiais. s´ que aplicado as mutexes.. } ´ Como e de costume encontrar por a´. at´ agora. Veremos agora os comportamentos mais interessantes associados com as vari´ veis a atributos das mutexes. Lembrem-se que podemos encontrar ¸˜ um conjunto de decis˜ es de escalonamento que possa fazer com que o valor lido esteja o inconsistente logo ap´ s a verificacao. return NULL. a a a mutex pode ser posta num espaco de mem´ ria compartilhado e ser utilizada por diversos ¸ o processo. a mutex reside num espaco de enderecamento ¸ ¸ local e somente o processo que criou a mutex pode utiliz´ -la. usamos a funcao pthread mutexattr destroy. J´ no escopo do sistema.. O ¸˜ a ´ primeiro e o endereco da mutex que desejamos inicialiar e o segundo. No escopo do processo. . s´ apa¸ e o ´ receu como nulo. devemos inicializ´ -las com a funcao ptha a ¸˜ ¸˜ read mutexattr init. ´ Ecopo da vari´ vel de exclus˜ o mutua a a Assim com as threads. pthread_mutex_init (&mutex). Para utiliz´ -las. Esse tipo seria equivalente ao pthread attr t. /* . o Para usarmos tais vari´ veis. pthread_mutex_t mutex. */ pthread_mutexattr_init (&mutexattr). as mutexes podem existir em dois contextos diferentes: processo ou sistema.pthread_mutex_unlock (&p->mutex). Para tal objetivo temos o tipo pthread mutexattr t.. Esse ultimo serve para que o comportamento das mutex seja alterado conforme a necessidade. */ ¸˜ pthread_mutex_init (&mutex. &mutexattr). Abstratamente: pthread_mutexattr_t mutexattr. Deve-se observar que at´ o acesso a vari´ vel Pilha->no na instrucao de controle e a ¸˜ de fluxo if da funcao Desempilhar foi sincronizado. basta passar seus enderecos como segundo parˆ metro da funcao ptha ¸ a ¸˜ read mutex init. Para destruirmos. /* .

a Explicar como utilizar uma mutex compartilhada entre processos foge do tema desse artigo. o segundo e o escopo da mutex. O primeiro parˆ metro e a a ´ vari´ vel atributo. O fluxo deve libera-l´ a a mesma quantidade de vezes para que outro fluxo possa adquiri-l´ . ou empasses fatais. Uma segunda tentativa de aquisicao numa mutex a ¸˜ desse tipo causar´ uma situacao de inanicao (o fluxo permanecer´ eternamente a ¸˜ ¸˜ a bloqueado). A saber: R´ pidas (fast mutex): As mais simples. s˜ o situacoes onde o progresso do fluxo de a ¸˜ execucao de uma processo ser´ interrompido devido ao mesmo permancer aguardando ¸˜ a um evento que nunca ocorrer´ . Deadlocks. ela n˜ o necessita a estar no escopo do sistema!!! ´ A funcao usada para determinar o escopo da mutex e pth¸˜ ´ read mutexattr setpshared (pthread mutexattr t*. Ou seja. foram propostos diversos tipos de mutexes. a ´ Tipo da vari´ vel de exclus˜ o mutua a a Para entende o por que de se ter diversos tipos de mutexes. Os ı e tipos mais sofisticados de mutexes nada mais s˜ o que uma extens˜ o do tipo r´ pido. PTHREAD PROCESS SHARED tora nar´ poss´vel o uso da mutex por diversos processo. Por padr˜ o as mutex residem num escopo local. o fluxo ser´ interrompido at´ que o fluxo que est´ com o controle da mutex a libere. por que usar uma mutex r´ pida se isso tornar´ a a o processo de descoberta de erro mais dif´cil? Uma das respostas seria eficiˆ ncia. mas quem quiser saber mais um pouco e ainda ver uma simples e claro exemplo pode dar uma olhada nas p´ ginas de manual de (3p) pthread mutexattr init. e Para evitar problemas relacionados com empasses. caso n˜ o tenhamos uma implementacao de PThreads com suporte a recurs˜ o. Vale enfatizar que a comportamentos arbitr´ rios ocorrer˜ o caso se tente usar uma mutex privada entre diversos a a processos. o Muitas vezes fica a d´ vida: se temos como evitar deadlocks com o uso de mutex u recursivas ou com checagem de erros. a Um exemplo simples de deadlock s˜ o observados com o uso de mutexes. J´ PTHREAD PROCESS PRIVATE a ı a impedir´ qualquer tentativa de uso da mutex por outro processo. int). a Checagem de Erro (error checking mutex): Uma segunda tentativa de adquirir essa mutex retorna o c´ digo de erro EDEADLK. faz-se necess´ rio ena tender o conceito de deadlock. Por a a a exemplo. Recursivas (recursive mutex): Sendo um pouco mais sofisticadas.Observe que o escopo de uma mutex s´ est´ relacionado com o espaco de o a ¸ enderecamento que a mesma reside. a ¸˜ a podemos fazˆ -lo da seguinte forma: e typedef struct rmutex { . a e a ´ Mas o fluxo interrompido e o fluxo que deve liberar a mutex! Uma esp´ cie de paradoxo. para que uma mutex seja usadas por di¸ versos fluxos de escopo de sistema criadas por um mesmo processo. Um a questionamento simples: o que ocorre quando um mesmo fluxo de processamento tenta adquirir o controle de uma mutex duas vezes consecutivas sem a liberar? Deadlock! Ou seja. essas mutexes lembram quantas vezes foram adquirias por um mesmo fluxo.

} pthread_mutex_unlock (&mutex->check). pthread_mutex_unlock (&mutex->lock). pthread_mutex_unlock (&mutex->check). . if (mutex->id == pthread_self()) { mutex->count++. pthread_mutex_lock (&mutex->check).pthread_mutex_t check. } pthread_mutex_unlock (&mutex->check). } pthread_mutex_unlock (&mutex->check). mutex->id = pthread_self (). pthread_t id. pthread_mutex_lock (&mutex->lock). mutex->count = 1. return 0. pthread_mutex_unlock (&mutex->check). if (mutex->id == pthread_self()) { if (!--mutex->count) { mutex->id = NULL. int count. return 0. int RMutexLock (RMutex* mutex) { pthread_mutex_lock (&mutex->check). } int RMutexUnlock (RMutex* mutex) { pthread_mutex_lock (mutex->check). return 0. pthread_mutex_t lock. } RMutex.

o sistema pode mais uma vez sofrer por inanicao. Digamos tamb´ m que essa condicao o ¸˜ e ¸˜ ser´ estabelecida por uma outra thread e que a verificacao da mesma envolva o acesso a a ¸˜ uma regi˜ o cr´tica. } N˜ o entraremos na discuss˜ o do c´ digo. Permitem especificar situacoes mais complexas acerca da ¸˜ ¸˜ ´ execucao das threads. as mutexes reo a a cursivas trazem consigo algumas complicacoes. } pthread_mutex_unlock (&mutex). ¸˜ ¸˜ 2. As vari´ veis condicionais s˜ o uma mecanismo um pouco mais poderoso para a a a sincronizacao das threads. evitando que o processador seja ¸˜ ´ alocado para alguma outra thread. As mutexes com checagem de erro chegam at´ a ter uma implementacao mais e ¸˜ simples. poderiamos fazer: a ı while (1) { pthread_mutex_lock (&mutex). return 1. o c´ digo possui um problema simples: carece o de espera ociosa. serve apenas para mostrar que o que foi a a o dito acima. Vejam que foi necess´ rio manter um contador.return 1.2. } Apesar de funcionalmente correto. al´ m de garantir que a espera seja feita no ocio. a Enquanto uma mutex com checagem de erro provem apenas um mecanismo simpl´ rio que n˜ o altera muito o comportamento das mutexes r´ pidas. E justamente aqui que as vari´ veis condicionais ena tram. Ou seja. podemos ver que ¸˜ o a mutex lock s´ ser´ liberada quando a vari´ vel count for igual a zero. as threads que aguardar˜ o a manutencao da condicao ir˜ o a ¸˜ ¸˜ a permanecer executando as instrucoes presentes no loop. Enfatizando. Caso n˜ o hajam o a a a liberacoes suficientes. Utilizando uma mutex. if (condicao) { /* faz alguma coisa */ pthread_mutex_unlock (&mutex). e apenas uma ilustracao que as mutexes mais sofisticadas podem derivar das mutexes ¸˜ r´ pidas. Vari´ veis Condicionais a Digamos que temos uma determinada regi˜ o de c´ digo que a thread s´ dever´ executar a o o a ap´ s uma determinada condicao se tornar verdadeira.3. uma identificacao de qual thread a ¸˜ adquiriu o mutex e ainda uma mutex extra para que as verificacoes b´ sicas possam ser ¸˜ a feitas. No c´ digo de exemplo. ¸˜ e . essa n˜ o e a implementacao das mutexes recursivas nativa do PTha ´ ¸˜ ´ read.

Antes de a a ´ utiliz´ -los. devido ao uso a e das vari´ veis condicionais. liberam a ¸˜ mutex passada como segundo argumento. O primeiro argumento de ambas as funcoes e a vari´ vel argumento a qual se deseja sinalizar. struct timespec *) podem ser usadas para aguardar a condicao. Caso deseje apenas uma vari´ vel condicional padr˜ o. if (!condicao) { pthread_cond_wait (&cond. as funcoes pthread cond wait (pthread cond t *. O a a ´ motivo e a guarnicao de um predicado que dever´ manter-se durante toda a execucao. } else { pthread_mutex_unlock(&mutex).html 2 tipo struct timespec veja . como de praxe. ptho ¸˜ read mutex t *) e pthread cond timedwait (pthread cond t *. Os primeiros argumentos de ¸˜ ambas s˜ o idˆ nticos: uma vari´ vel condicional j´ inicializada e uma mutex inicializada a e a a adquirida pela thread atual. Ap´ s inicializada. &mutex). A primeira forma de inicializacao se mostra necess´ ria ¸˜ a apenas quando deseja-se modificar o comportamento da vari´ vel em meio ao uso de uma a vari´ vel atributo. pth¸˜ read cond broadcast ir´ desbloquear todas as threads que estiverem aguardando pelo a sinal.. por´ m. As duas possuem uma diferenca semˆ ntica bastante ¸ a ´ a o sucinta: pthread cond signal ir´ desbloquear uma unica thread.org/onlinepubs/007908799/xsh/time. como no c´ digo acima sempre haver´ uma regi˜ o cr´tica. Para sinalizar as threads que est˜ o bloquadas na vari´ vel condicional s˜ o proa a a vidas duas func˜ es: pthread cond signal (pthread cond t*) e pthread cond broadcast o ¸˜ ´ a (pthread cond t *). A funcao pthread cond timedwait ir´ bloquear somente en¸˜ a quanto a hora do sistema for menor que o valor espec´ficado pelo seu terceiro argumento. o a a ı O tipo associado com as vari´ veis condicionais s˜ o o pthread cond t. Ent˜ o. as vari´ veis condicionais est˜ o sempre acompanhadas de mutexes.h. o c´ digo exemplo dessa sess˜ o pode ser re-escrito da seguinte maneira: a o a pthread_mutex_lock (&mutex). por sua vez.. a atribuicao e mais a a a ¸˜ ´ eficiente. as funcoes pthread cond wait e pthread cond timedwait. return -1. } Esse fragmento possui a mesma semˆ ntica que o anterior.Todavia. pthread condattr t *) ou atribuir o valor PTHREAD COND INITIALIZER. pthread mutex t *. n˜ o e necess´ rio a permanˆ ncia em um looping infinito nem a a ´ a e Para um melhor detalhamento do http://opengroup. e necess´ ria sua inicializacao em meio ao uso da funcao a a ¸˜ ¸˜ pthread cond init (pthread cond t *. } if (condicao) { /* Faz algo.2 ı Quando chamadas. Em ¸˜ a ¸˜ outras palavras. aleat´ riamente mas levando em consideracao as prioridades associadas a cada thread. */ pthread_mutex_unlock(&mutex).

Simples e f´ cil. sinalizar para as threads que est˜ o aguardando o sinal.muito menos realizar constantes aquisicoes e liberacoes da mutex. Mas como essa e uma situacao recorrente nos programas pa¸˜ a ¸˜ ralelos/multithread. Ou seja. No nosso caso. */) e logo em ¸˜ a seguida liberar´ a mutex. fica como tarefa de a a casa. a thread verifica logo em seguida se a vari´ vel condia a cao possui valor diferente de zero. ¸˜ ¸˜ ´ Analisando o c´ digo. Os sem´ foros. por exemplo. Caso o valor da vari´ vel condicao seja zero. pthread_mutex_unlock (&mutex).3. ¸˜ a A thread emissor´ do sinal de execucao possuir´ um c´ digo mais simples.. Ser´ apresentado o comportamento e a a dos sem´ foros a seguir. portanto. As funcoes wait e post. alterar os dados compartilhados a ´ de forma a garantir a condicao de execucao. Em outras palavras: wait espera e post d´ a largada. Existem tamb´ m . resolveu-se criar uma estrutura de dados para trata-l´ : os sem´ foros. ptha read cond wait ir´ por a thread para escutar na vari´ vel condicional cond e liberar´ a a a a mutex para que a thread emisora do sinal de execucao possa modificar os dados. est˜ o implementadas como ¸˜ a e int sem wait(sem t *sem) e int sem post(sem t *sem). podendo at´ ”imitar”o comportamento de diversos outros ¸˜ ¸ e mecanismos de sincronizacao. condicao = facaalgo().. primeiramente vemos que e feita a aquisicao da mutex pela o ¸˜ thread. pthread_cond_signal (&cond). Sem´ foros a ´ Os sem´ foros trabalham como sinalizadores: seu principal uso e para representar a dispoa nibilidade de um recurso computacional. uma consumidora dos elementos da fila e outra produtora desses elementos. respectivamente. a As boas pr´ ticas (e o manual) dizem que mesmo recebendo a sinalizacao de uma a ¸˜ outra thread. quem quiser tentar implement´ -los. a ´ O exemplo mostrado e simples. o c´ digo do bloco de o o execucao ir´ alterar os dados compartilhados entre as threads (/* Faz algo. A wait faz com que ¸˜ a thread bloquei at´ que outra thread sinalize no sem´ foro. mais uma comparacao foi realizada na vari´ vel condicao. deve-se re-avaliar os predicados envolvidos. a a Essas estruturas de dados possuem duas funcoes. e liberar a mutex. na biblioteca PThreads. Uma das maneiras de garantir a espera ociosa nesse situacao seria ¸˜ criar uma vari´ vel condicional e usar o contador de elementos da fila como condicao de a ¸˜ ´ execucao. s˜ o muito simples de imple¸˜ a a mentar atrav´ s de mutexes e vari´ veis condicionais. tarefa feita atrav´ s da funcao e a e ¸˜ post. o emissor precisar´ adquirir a mutex. post e wait. 2. a ´ O primeiro passo para utilizar os sem´ foros e incluir o arquivo de cabecalho sea ¸ maphiore. mas com um pouco de imaginacao e poss´vel criar ¸˜ ´ ı diversas condicoes avancadas..3. que em nosso exemplo e apenas a vari´ vel ¸˜ ¸˜ a condicao.. seja uma fila dinˆ mica controlada a por uma mutex e duas threads. que no a ¸˜ a o nosso exemplo seria algo como o fragmento a seguir: pthread_mutex_lock (&mutex). ehehehe. com a mutex em m˜ os. Caso o ¸˜ valor seja diferente de zero ou ap´ s o retorno de pthread cond wait. Por exemplo.h.

as funcoes sem trywait. precisamos inicializ´ -lo. /* Func˜o de produc˜o */ ¸a ¸a produzir(). pthread_mutex_init (&mutex. E um erro horr´vel utilizar um a ı ´ sem´ foro selvagem e e bem dif´cil de visualizar o erro. 0). o ultimo a a ´ ´ argumento e o valor inicial do contador do sem´ foro. a ı N˜ o e necess´ rio um exemplo complexo para entender o uso dos sem´ foros. vai um bem abstrato e simples. O valor 0 (zero) indica que o sem´ foro ser´ utilizado apenas pelas threads de um mesmo processo. /* Sem´foro compartilhado entre as threads de a um mesmo processo e com o contador vazio. unsigned int). o manual on-line tem uma otima explicacao. } /* Excerto da Thread Consumidora */ while (1) { sem_wait(&semaforo). sem_post(&semaforo). O primeiro argumento e o ´ sem´ foro a ser inicializado. a ´ a a Ent˜ o. */ pthread_t mutex. /* Excerto da Thread Produtora */ while (1) { pthread_mutex_lock(&mutex). pthread_mutex_unlock (&mutex). 0. Tomem cuidado com essa parte. A funcao respons´ vel a a ¸˜ a ´ ´ por essa tarefa e a int sem init(sem t *. ¸˜ ¸˜ Antes de utilizar um sem´ foro. o segundo e uma flag para indicar se o sem´ foro dever´ ser a a a compartilhado entre multiplas threads ou multiplos processos. Para ´ maiores informacoes sobre ambas. int. a /* Nosso sem´foro */ a sem_t semaforo. que verifica se o contador do sem´ foro est´ positivo e caso esteja ¸˜ a a decrementa-o mas n˜ o bloqueia caso o contador esteja em zero. NULL). em sua maioria. s˜o acompanhados a a de mutexes. pthread_mutex_lock(&mutex). que a permite especificar um intervalo de tempo no qual a thread pode ficar bloqueada. /* Sem´foros. */ sem_init (&semaforo. /* Func˜o de consumo */ ¸a . e sem timedwait.

consumir(). a thread consumidora ser´ desboquea a ada. N˜ o e necess´ rio dizer que e ¸˜ e a ´ a o contr´ rio. inanicao (starvation em inglˆ s). Mas como nem tudo na vida e t˜ o f´ cil. os diversos pesquisadores da area criaram proble¸˜ mas no m´nimo peculiares. Ainda n˜ o sacou por que esse e um problema o a de sincronizacao? Imagine se cada fil´ sofo. para comer o espagheti. Considere que est˜ o sentados a mesa N fil´ sofos e que est˜ o a o a dispon´veis N garfos. usando a ı a vari´ vel de exclus˜ o m´ tua mutex para impedir inconsistˆ ncias. ou o termo a o a t´ cnico advindo da biologia.h> #define ESQUERDA(f) ((f + nfilosofo . que apesar de funcional. s˜ o a necess´ rios dois garfos! a A´ vem a pergunta: como isso pode ser modelado num problema de sincronizacao? ı ¸˜ De forma bem simples. Problemas Cl´ ssicos de Sincronizacao a ¸˜ Visando ilustrar melhor os problemas enfrentados pelos desenvolvedores relativos a ´ sincronizacao entre threads/processos. a a u e 3. #include <stdio. Ocorrer´ o que a a a chamamos de deadlock: cada um dos fil´ sofos segurar´ um garfo e n˜ o ir´ solt´ -lo at´ o a a a a e que o fil´ sofo a sua esquerda disponibilize outro garfo. ao mesmo tempo. S´ que nenhum outro fil´ sofo o o o soltar´ seu garfo! Portanto. pthread_mutex_unlock (&mutex). } Bem simples: a thread consumidora vai ficar esperando no sem´ foro semaforo e a quando a thread produtora sinalizar nessa vari´ vel. O c´ digo est´ ¸˜ o a abaixo. a Existem diversas abordagens que resolve esse problema. pegar o garfo a sua direita ¸˜ o e n˜ o solt´ -lo enquanto aguarda a disponibilidade do garfo a esquerda. todos os fil´ sofos morrer˜ o por falta de comida.1.h> #include <pthread. Irei focar-me aqui numa mais simples. pegar os garfos a esquerda e esperar o da direta. Considerando que n˜ o vale usar as m˜ os. uma das maiores mentes da computacao. o jantar dos fil´ sofos o consiste num suculento e escorregadio espagheti.h> #include <stdlib. Vou comentar e mostrar a solucao de dois deles: o jantar dos ı ¸˜ fil´ sofos e o barbeiro dorminhoco. Ele baseia-se na generalizacao de que os fil´ sofos s´ fazem duas coisas na ¸˜ ¸˜ o o ´ a a vida: comer e pensar. podermos ter no m´ ximo ı a a a ´ N/2 fil´ sofos comendo ao mesmo tempo.1) % nfilosofo) #define DIREITA(f) ((f + 1) % nfilosofo) .h> #include <unistd. foje um pouco da solucao ideal. tamb´ m leva a uma situacao a e ¸˜ problem´ tica. as threads entram numa sess˜ o cr´tica. O Jantar dos Fil´ sofos o Esse problema foi proposto em 1965 por Edsger Dijkstra. Antes de produzir e antes de consumir. tanto que. o 3.

pthread_mutex_unlock(&garfos). } Filosofo.. Vou tentar pegar os garfos!\n". fprintf(stdout. COMENDO. pthread_mutex_unlock(&garfos). "F[%d]: Estou com fome.. Nao deu pra mim. "\tFilosofo %d estah comendo.. while (1) { pthread_mutex_lock (&garfos). pthread_mutex_t garfos.. ESQUERDA(fid)).. "F[%d]: Estou pensando.. . :-( \n". fprintf (stdout.. case COMFOME: fprintf(stdout.#define MSLEEP 5 #define SLEEPTIME (random()%MSLEEP) typedef enum { COMFOME. sleep(SLEEPTIME). "\tFilosofo %d estah comendo.\n". fid)... void *ffuncao(void *f) { int fid = (int) f... Filosofo *filosofos. } else { filosofos[fid] = COMENDO. fprintf (stdout.. if (filosofos[ESQUERDA(fid)] == COMENDO) { pthread_mutex_unlock(&garfos). Nao deu pra mim. PENSANDO. } else if (filosofos[DIREITA(fid)] == COMENDO) { pthread_mutex_unlock(&garfos). switch (filosofos[fid]) { case PENSANDO: filosofos[fid] = COMFOME. break. int nfilosofo. fid). :-( \n". DIREITA(fid)).

sizeof(Filosofo)). argv[1]). a return 1. pthread_t *t.fprintf (stdout. 10). break. "\tAeee! Vou encher o bucho! :-)\n"). } filosofos = (Filosofo *) calloc (nfilosofo. . break. case COMENDO: filosofos[fid] = PENSANDO.\n").. sizeof(pthread_t)).. } sleep(SLEEPTIME). char **argv) { int i. fid). Hora de voltar a pensar. "Argumento inv´lido: %s\n". if (endptr) { fprintf (stderr.. } } else { nfilosofo = 5. } } } int main (int argc. fprintf(stdout..\n". if (argc > 1) { char **endptr = NULL. } t = (pthread_t *) calloc (nfilosofo.. a return 1. o a N˜o querem pensar hoje. "F[%d]: Enchi o bucho. pthread_mutex_unlock(&garfos). sleep(SLEEPTIME). nfilosofo = (int) strtol(argv[1]. endptr. "OS fil´sofos est˜o cansados.. if (!filosofos) { fprintf(stderr.

NULL).. Quando nesse estado.. "Executar ou n˜o executar . lida e modificada concorrentemente por todas as threads. a return 1. utilizando espera ociosa e ¸˜ tudo o mais. De acordo com o c´ digo.2. apresenta em seu livro Sistemas Operacionais Modernos uma solucao bem mais sofisticada. } return 0. PENSANDO e COMENDO. o fil´ sofo o a o continua no estado de COMFOME e espera um tempo arbitr´ rio para tentar novamente. no estado COMFOME. famoso por criar o Minix. Caso ambos estejam PENSANDO ou o COMFOME. e No in´cio da execucao. A Wikip´ dia. (void *) i). os fil´ sofos s˜ o postos no es¸˜ o a tado de FOME. pthread_create (&t[i]. Ela e a funcao encarregada por ¸˜ o ¸˜ manter consistente os estados dos fil´ sofos. ı ¸˜ o a como visto na linha 101. i++) { filosofos[i] = PENSANDO. a ´ Notem tamb´ m que e importante liberar a mutex t˜ o logo o trabalho necess´ rio e a a ´ seja realizado. eis a quest˜o!\n"). Recomendo a todos dar uma olhada. Caso contr´ rio. Ao iniciar a funcao ffuncao. i < nfilosofo . mas n˜ o se importa por ser acordado pelos a clientes. . } pthread_mutex_init (&garfos. podemos ver que os fil´ sofos poder˜ o estar em o o a trˆ s estados distintos: COMFOME. NULL. em inglˆ s. ffuncao. o problema do barbeiro dorminho tamb´ m apreı e e senta uma inusitada analogia. a est´ protegida pelo mutex garfos. Dessa vez. O Barbeiro Dorminhoco Atribu´do tamb´ m ao Edsger Dijkstra. a . est˜ o representados pela o a enum filosofo. Recomendo-a tamb´ m. o problema envolve um barbeiro que sempre aproveita o tempo livre para tirar uma soneca. que por sua vez. a Observe que a vari´ vel filosofo.. a mutex e liberada t˜ o logo o pr´ ximo a o estado do fil´ sofo possa ser inferido.if (!t) { fprintf (stderr. tamb´ m tem e e e algumas solucoes diferentes da mostrada aqui. } ´ ´ A principal funcao do nosso c´ digo e a ffuncao. Por exemplo. for (i = 0 . } for (i = 0 . o fil´ sofo atual entra no estado de COMENDO. todos os fil´ sofos s˜ o postos no estado PENSANDO. o fil´ sofo que est´ executando observa o fil´ sofo o a o a sua esquerda e em seguida o fil´ sofo a direta.. NULL). i < nfilosofo . ¸˜ e 3. o O Andrew Tanenbaum. i++) { pthread_join(t[i].

pthread_mutex_t mlugares. com P % de a probabilidade de entrarem na barbearia.h> <signal. int probabilidade. e a ı os novos clientes v˜ o embora. com N barbeiros... } pthread_mutex_lock (&mlugares). o cliente seja a a atendido pelo barbeiro. Se n˜ o houver clientes. int nbarbeiros. "Barbeiro[%d]: N˜o tem nenhum a cliente. que apesar de tudo a solucao e t˜ o f´ cil a ¸˜ ´ a a quanto a do problema simples.h> <stdlib. void *fbarbeiro (void *b) { int bid = (int) b. sem_t slugares. int q. O c´ digo est´ abaixo. int dlugares.´ Nossa miss˜ o nesse problema e garantir que t˜ o logo quanto chegue. o a #include #include #include #include #include #include <pthread. sem_wait(&slugares)..h> <errno. q = --dlugares.. break. os novos clientes se sentar˜ o nas a cadeiras de espera at´ que sejam atendidos..h> #include <semaphore. Os clientes chegar˜ o a cada 1 segundo. zZzzZzZ.. bid). } fprintf (stdout. . Caso o barbeiro esteja ocupado.\n\tVou dormir. Se n˜ o houver nenhuma cadeira dispon´vel.h> <stdio.\n\n". Nossa implementacao ser´ baseada ¸˜ a numa vers˜ o mais sofisticada.h> <unistd. a a ´ O problema mais simples. e conhecido tamb´ m e como o problema do barbeiro dorminhoco simples.h> int nlugares. o barbeiro vai tirar uma soneca. while (1) { if (sem_trywait (&slugares) == -1) { if (errno != EAGAIN){ perror("Erro"). onde existe apenas um barbeiro.

if (dlugares < nlugares) lugardiponivel = 1.\n\n").q). "Um novo cliente chegou. } void gerarclientes() { int q. . "Barbeiro[%d]: Atendendo cliente. } pthread_mutex_unlock (&mlugares)..\n\t Ele est´ aguardando sua vez. nlugares .\n\t Ainda existem %d clientes na fila. else lugardiponivel = 0.\n\n"). fprintf (stdout..\n\t O sal˜o est´ lotado. a } else { fprintf (stdout.pthread_mutex_unlock (&mlugares).. Ele vir´ outro a a a dia. bid. } fprintf (stdout. while (1) { if ((random() % 100) < probabilidade) { pthread_mutex_lock (&mlugares). if (lugardiponivel) { q = ++dlugares. "Existem %d clientes na fila e %d lugares dispon´veis. q). int lugardiponivel.\n\n"..\n\n".. ı q. sem_post(&slugares). sleep (2). } return NULL. if (lugardiponivel) { fprintf (stdout. "Um novo cliente chegou. sleep (random() % 5)..

\ if (endptr) {\ fprintf (stderr.\ var = strtol (argv. SIGINT). "N˜o foi poss´vel mascarar os sinais!"). &set.\ a . argv). } } int bloquearsinais () { sigset_t set. sigaddset(&set. sigemptyset(&set). "N˜o foi poss´vel mascarar os sinais!"). a ı return 1. sigaddset(&set. SIGINT). sigemptyset(&set). SIGTERM). if (pthread_sigmask(SIG_BLOCK. NULL)) { fprintf (stderr. sigaddset(&set. } int liberarsinais () { sigset_t set. "Argumento inv´lido: %s\n". 10). SIGTERM).var) \ {\ char **endptr = NULL. &set. if (pthread_sigmask(SIG_UNBLOCK. SIGQUIT). sigaddset(&set. sigaddset(&set.} sleep(1). } return 0. SIGQUIT). a ı return 1. } return 0. } #define clp(argv. endptr. sigaddset(&set. NULL)) { fprintf (stderr.

} else { nlugares = 3. 0. pthread_t *barbeiros. } if (argc > 2) { clp(argv[2]. } pthread_mutex_init (&mlugares. NULL). } if (argc > 3) { clp(argv[3]. probabilidade). } int main (int argc.return 1. barbeiros = (pthread_t *) calloc (nbarbeiros. "Erro inicializando sem´foro\n"). \ }\ } int processarlc (int argc. } else { probabilidade = 80. if (!barbeiros) { . char **argv) { if (argc > 1) { clp(argv[1]. } else { nbarbeiros = 1. a return 1. } return 0. char **argv) { int i. sizeof(pthread_t)). nlugares). dlugares = 0. } if (sem_init (&slugares. argv)) { return 1. nbarbeiros). 0)) { fprintf (stderr. if (bloquearsinais() || processarlc(argc.

"Barbeiro[%d]: N˜o tem nenhum a cliente. } Na primeira linha. vari´ vel e a A geracao de novos clientes e o trabalho do barbeiro foi simplificado ao m´ ximo ¸˜ a para que o exemplo fique bem did´ tico.. pois seria a ı mais adequado lugares ocupados. a quantidade de clientes aguardando ser´ representado pela ¸˜ a vari´ vel dlugares (dispon´vel lugares).. para executar o programa com 3 lugares. 2 barbeiros e uma probabilidade de 90%. Mas para que tudo fique bem mais claro. gerarclientes(). i). O acesso a essa a ´ protegido pela mutex mlugares. break. } ´ Esse programa recebe at´ trˆ s argumentos. return 1. } fprintf (stdout..\n\tVou dormir..\n". Portanto. } liberarsinais(). vamos a analisar a fundo as funcoes. basta fazer: $ . o n´ mero de barbeiros e a probabilidade do cliente entrar na a u loja.. "Nenhum barbeiro vir´ trabalhar a hoje. (void *)i). sem_wait(&slugares). fbarbeiro. i < nbarbeiros .fprintf (stderr. A fbarbeiro faz o trabalho dos N barbeiros../barbeiro 3 2 90 Em nossa solucao. com o uso da funcao sem trywait. NULL. pthread_create (&barbeiros[i].. bid). o qual ser´ usado a a para sinalizar a chegada de um novo cliente. } for (i = 0 . Sei que ficou um pouco contra-intuitivo.\n"). return 0. "O barbeiro %d chegou para trabalhar.. O motivo ¸˜ .\n\n". O primeiro deles e a quantidade de e e clientes que poder˜ o esperar. A parte principal ¸˜ do c´ digo se encontra reproduzido abaixo: o if (sem_trywait (&slugares) == -1) { if (errno != EAGAIN){ perror("Erro"). zZzzZzZ. i++) { fprintf (stdout. mas d´ para sobreviver com isso. tentamos decrementar o sem´ foro slugares.

o barbeiro procura fazer seu trabalho: pthread_mutex_lock (&mlugares). o que ocasiona alguns incovenientes. Quem fizer pleno uso dos conhecimentos presentes aqui e no a ´ ALP.org/wiki/Sleeping barber ´ Recomendo a leitura de cada uma delas. para nosso exemplo. comprem o livro! E muito bom e vale cada centavo! .html 3. Ent˜ o. Wikipedia.org/wiki/Dining philosophers problem 7. Sleeping Barber Problem. www. 2001. que n˜ o alteram em nada ¸˜ a a a o prop´ sito de passar uma nocao mais pr´ tica dos mecanismos de sincronizacao.t-labs. a ı e ¸˜ ´ e por o barbeiro para dormir. q = --dlugares.net.advancedlinuxprogramming. Oldham e Samuel.com 2. Mas para os a a ´ clientes n˜ o ficarem chegando o tempo todo e n˜ o se possa observar a acao de dormir. pois ningu´ m e de ferro! a e ´ ´ Assim que e acordado.html 4. Simplesmente decrementamos a vari´ vel dlugares e guardamos seu valor atual na a vari´ vel q para apresentar uma mensagem simples na tela.´ para tal e que com ela podemos tentar decrementar o sem´ foro e realizar uma acao suba ¸˜ sequente caso n˜ o seja poss´vel fazˆ -lo no momento. Wikipedia. New Riders. devemos nos concentrar no trabalho a ser realizado e liberar a mutex t˜ o a ı a cedo quanto poss´vel! Outras threads estar˜ o esperando para executar.wikipedia.cs. As outras funcoes s˜ o apenas aspectos n˜ o-funcionais. Threads Scheduling with pthreads under Linux and FreeBSD. o a Uma parte interessante do c´ digo diz respeito a recepcao de sinais advindos do o ¸˜ sistema operacional. As funcoes bloquearsinais e liberarsinais se encarregam da tarefa ¸˜ de evitar que alguns sinais cheguem as threads e liberar esses sinais para outras threads. pthread_mutex_unlock (&mlugares). nossos barbeiros esperar˜ o sentados e dormindo. Maier. enviandos sinais que as threads n˜ o est˜ o a a a preparadas para tratar.ac. enquanto n˜ o chegar um novo a ¸˜ a a cliente. Pthreads: semi-FAQ Revision 5. https://computing. Marshall. gerar clientes. Recomendacoes de Leituras ¸˜ Apesar de n˜ o estarem expl´citas no texto.net/html/howto/pthreadSemiFAQ. quando numa a regi˜ o cr´tica.wikipedia. tamb´ m e bem simples. Lampkim. http://www. e a a ¸˜ utlizado um valor randˆ mico para determinar se um novo cliente entra na loja ou n˜ o. http://en.gov/tutorials/pthreads/ 5.2.cognitus.llnl. Advanced Linux Programming. ´ respectivamente. POSIX Threads Programming. Dining Philosopher Problem. que n˜ o passa de imprimir uma mensagem na tela e pˆ -lo a o para esperar no sem´ foro com a funcao sem wait. A referˆ ncia 1 e a que mais recomendo. Futher Threads Programming: Thread Attributes. o ¸˜ a ¸˜ 4. Barney. http://en. Lembrem-se.html 6.tu-berlin.uk/Dave/C/node30. e Ela possue vers˜ o impressa.cf. ı a O trabalho realizado pela thread principal. O motivo para uso de tal abordagem e que o sistema operacional muitas vezes atrapalha o funcionamento dos sem´ foros. Nossa acao.de/ gregor/tools/pthread-scheduling. Mitchel. essas referˆ ncias foram muito importantes para a ı e a consolidacao do mesmo: ¸˜ 1. e ´ Ela somente incrementa a vari´ vel dlugares a sinaliza no sem´ foro slugares. http://www. http://www.

por´ m vale a referˆ ncia pois apresentam conceitos importantes: e e 1. ı 2. . Prentice Hall. 2 ed. Nota: cap´tulo 8. Organizacao Estruturada de Computadores. 2003. Sistemas Operacionais Modernos.As pr´ ximas referˆ ncias n˜ o tratam diretamente sobre processamento multithread o e a ou paralelo. Prentice Hall. Tanenbaum. Arquitetura de Computadores Paralelos. Tanenbaum. 5 ed. ¸˜ 2007.