Forks & pthreads

Existem dois métodos de executar tarefas simultâneas com o Linux. Uma é usando a função fork(), que já mostrei por aqui. Outra maneira é usando threads, através da biblioteca pthreads.

Threads podem ser entendidas como processos “leves”. Quando usamos fork(), todo o espeço de dados, os descritores de arquivos e outros atributos do processo original são copiados para o processo filho. Isso faz de fork() uma função muito cara, que exige muito, por assim dizer, do sistema operacional. Threads, por outro lado, existem no mesmo espaço de endereçamento do processo original. Usar threads implica em não ter que criar um novo processo e, ao mesmo tempo, compartilhar todos os recursos do processo.

No entanto, threads têm um ponto fraco: sincronismo. Você tem que tomar cuidado em proteger espaços de memória compartilhados por duas ou mais threads usando ferramentas de controle de sincronismo, como mutexes, por exemplo. Mas, deixo isso para demonstração futura (próxima).

Usar pthreads não poderia ser mais simples… Primeiro, definimos a nossa função que será executada em paralelo. A assinatura da função é sempre a mesma:

void *func(void *data);

Ou seja, a função toma um ponteiro genérico e retorna um ponteiro genérico. Esse ponteiro passado como parâmetro apontará para os dados que a função precisa para ser executada – se usarmos algum parâmetro… Podemos, é claro, obter os parâmetros da função a partir de uma variável “global”, se quisermos. Uma vez definida a função, basta criar a thread usando a função pthread_create():

#include <pthread.h>

void *myThread(void *data)
{
  // faz alguma coisa aqui.

  // Retorna NULL ou um ponteiro qualquer válido...
  return NULL;
}

...
pthread_t tid;

if (pthread_create(&tid, NULL, mythread, NULL) != 0)
{
  // Neste ponto poderíamos ler 'errno' para
  // obter o código de erro para a falha.

  fprintf(stderr, "Error creating thread.\n");
  exit(EXIT_FAILURE);
}
...

Uma vez criada a thread ela é posta em execução imediatamente. O primeiro parâmetro é o ponteiro do descritor que identifica a thread criada. O segundo, um ponteiro para uma série de atributos que, se não usados, deve apontar para NULL (falarei dos atributos em outra ocasião). O terceiro parâmetro é o ponteiro para a função que queremos paralelizar e o último parâmetro é aquele que será passado para a função em questão (no caso acima, nenhum).

Todo processo tem uma thread principal. Para esperarmos pelo término da thread criada, podemos pedir para a pthread library “juntá-la” à thread principal:

pthread_join(tid, NULL);

Essa função ficará bloqueada (em espera) até o término da thread identificada pelo descritor “tid”.

Podemos informar, na função da thread que está em execução, que a mesma deve ser “desatachada” da thread principal, assim não precisamos “juntá-la”… No corpo da função mythread(), acima, em algum lugar, poderíamos ter:

pthread_detach(pthread_self());

Onde pthread_self() devolve o descritor da própria thread (daí o “self”!). Uma desvantagem das threads “desatachadas” é que não temos como ler o valore retorno a partir da thread que a disparou. Mas, às vezes, é exatamente isso o que queremos!

Em resumo, é assim que usamos a biblioteca pthread. Existem muitas outras coisas nela. Vale a pena ler a manpage pthreads(7) e as outras listadas em SEE ALSO. Por enquanto devo dizer apenas que ambas as técnicas, a do fork() e a das pthreads, não são mutualmente exclusivas. Você pode usar ambas no mesmo código. De fato, aplicações como Apache, por exemplo, fazem exatamente isso (se você estiver usando a versão Worker do Apache, é claro!).

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s