Threads POSIX

Confira neste artigo as principais funções para criação e gerenciamento de threads especificadas pelo padrão POSIX, denominado Pthread!
threads

Olá, caro leitor. Este artigo tem como objetivo apresentar as funções básicas da biblioteca pthread. Assim, uma breve introdução sobre processos e threads é apresentada antes de apontar possíveis aplicações.

Sobre Processos e Threads

Um dos conceitos mais importantes relacionados a sistemas operacionais é denominado processo. De modo geral, um processo é uma abstração de um programa em execução. Tal programa possui um espaço de endereçamento e, em sistemas tradicionais, apenas um thread (segmento ou fluxo de controle) de execução. Além disso, o processo contém toda informação relacionada ao seu contexto de execução, por exemplo, o contador de programa, apontador de pilha e demais registradores. Dito de outra maneira, é um programa com com sua função principal, denominada main, sendo executado sequencialmente, instrução por instrução.

No entanto, em alguns sistemas é possível criar mais de um thread no mesmo processo, isto é, no mesmo espaço endereçamento. Nesse caso, mais de um fluxo de execução ocorre dentro do mesmo processo! Diante disso, é importante destacar algumas aplicações:

  • Tratar atividades que ocorrem “simultaneamente”;
  • Dividir a aplicação em tarefas que acessam recursos compartilhados;
  • Reduzir o tamanho de uma aplicação, uma vez que threads ocupam menos espaço em relação aos processos;
  • São mais fáceis de criar e destruir;
  • A sobreposição de tarefas pode acelerar a aplicação;
  • Possibilitam paralelismo real em sistemas multicore.

Principais diferenças

É importante destacar que threads e processos são conceitos diferentes. Como dito anteriormente, o processo é basicamente um agrupador de recursos (código e dados) e possui uma identidade, enquanto as threads são criadas no contexto de um processo e compartilham o mesmo espaço de endereçamento. À vista disso, threads não são independentes como os processos. Pois, embora compartilhem o mesmo espaço de endereçamento dentro de um processo, cada thread possui os mecanismos para gerenciar seu contexto de execução. Assim, threads possuem seu próprio contador de programa, apontador de pilha e registradores.

Contexto de threads

Os threads criados ocupam a CPU do mesmo modo que o processo criador, e também são escalonadas pelo próprio processo. Nesse contexto, quando uma aplicação multithread é executada, esses threads podem estar em qualquer um destes estados: em execução, bloqueado (aguardando), pronto para ser executado ou concluído (finalizado). Isso é ilustrado na Figura 1.

Estados de operação de uma thread.
Figura 1: Estados de operação de uma thread.

Portabilidade de Aplicações

Para padronizar a utilização de threads em diversos sistemas o IEEE estabeleceu o padrão POSIX threads (IEEE_1003.1c), ou Pthreads. Esse padrão define mais de 60 funções para criar e gerenciar threads. Tais funções são definidos na biblioteca pthreads.h. Além disso, a biblioteca define estruturas de dados e atributos para configurar os threads. De modo geral, esses atributos são passados como argumentos para os parâmetros das funções, por exemplo:

  • pthread_t: Handle para pthread, isto é, um valor que permite identificar o thread;
  • pthread_attr_t: Atributos para configuração de thread.

Esses recursos são utilizados nas principais funções para criação e gerenciamento de threads. Tais funções são apresentadas a seguir. Cabe ressaltar que as funções que retornam valor, tem como padrão o inteiro 0, indicando sucesso. Assim, qualquer inteiro diferente de zero é um código de erro.

Criando e destruindo threads

A função pthread_create é utilizada para inicializar um thread. Para isso, a função recebe como argumento o endereço de um dado pthread_t que será inicializado. Além disso, o endereço da função que será executada é informado no parâmetro start_routine. Tal função tem como retorno um valor void * e recebe apenas um argumento a partir do seu parâmetro void *. Esse argumento é passado para função start_routine especificando o último parâmetro da função pthread_create, denominado arg. Cabe ressaltar que essa função pode receber um atributo para configuração do thread. No entanto, esse argumento pode ser NULL, indicando que o thread será configurado conforme o padrão.

int pthread_create (pthread_t *thread, pthread_attr_t *attr, void *(*start_ routine) (void *), void
*arg);

Um thread pode ser finalizado a partir da função pthread_exit. Essa função recebe como argumento um endereço que é utilizado para armazenar o valor de retorno do thread.

void pthread_exit (void *retval);

Gerenciando threads

Considerando um ambiente em que mais de um thread estão sendo executados, pode ser necessário, em algum momento, aguardar a finalização de um procedimento. Isso pode ser realizado com a função pthread_join. A função pthread_join recebe como argumentos a estrutura de controle do thread, do tipo pthread_t, que será finalizado e o endereço de um ponteiro (void **) para o valor de retorno do thread.

int pthread_join (pthread_t thread, void **thread_return);

No contexto de execução do thread é possível obter o identificador do thread, denominado thread ID. Isso é realizado com a chamada pthread_self que tem como retorno um valor do tipo pthread_t.

pthread_t pthread_self (void); 

Como dito anteriormente, um thread pode estar em diversos estados. Em sua execução o thread pode indicar para gerenciador que o mesmo pode ser bloqueado. Isso é realizado pela função sched_yield. Nesse caso, um outro thread entrará em execução.

int sched_yield (void);

Exemplos

Para exemplificar alguns conceitos apresentados e destacar a utilização da biblioteca pthreads, alguns programas em C foram desenvolvidos e compilados usando o GCC no ambiente Linux.

O primeiro exemplo demonstra a criação de threads e suas funções de gerenciamento, o objetivo é apenas mostrar a utilização das funções.

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

/*Rotina que será executada*/
void * routine(void *arg);

int main (int argc, char *argv[])
{
	pthread_t thread_id;
	void * thread_res;
	int rstatus;
	
	/*tenta iniciar o thread, indicando a função 'routine' e passando como argumento a string "Minha primeira Thread!"*/
	rstatus = pthread_create (&thread_id, NULL, routine, (void*)("Minha primeira Thread!"));

	/*verificar se ocorreu algum erro na chamada de pthread_create*/
	if(rstatus != 0)
	{
		printf ("Erro ao criar o thread.\n");
		exit(EXIT_FAILURE);
	}

	printf ("Thread criado com sucesso!\n");

	/*aguarda finalização do thread identificado por thread_id. O retorno é passado pelo ponteiro thread_res*/
	rstatus = pthread_join (thread_id, &thread_res);

	/*verificar se ocorreu algum erro na chamada de pthread_join*/
	if(rstatus != 0)
	{
		printf ("Erro ao aguardar finalização do thread.\n");
		exit(EXIT_FAILURE);
	}

	/*exibe o valor de retorno da função 'routine'*/
	printf ("Thread finalizado! Retorno = %s\n", (char *)thread_res);

	return 0;
}

void * routine(void *arg)
{
	/*exibe o argumento recebido*/
	printf("Argumento: %s\n", (char *)arg);

	/*finaliza a função retornando o argumento que foi recebido*/
	pthread_exit(arg);
}

Para compilar esse arquivo, a seguinte linha de comando foi utilizada:

$ gcc main.c -o main -lpthread

Já a execução do programa:

$ ./main

O resultado da execução do programa é mostrado abaixo. Como esperado, o thread é criado e a aplicação principal aguarda sua finalização.

Thread criado com sucesso!
Argumento: Minha primeira Thread!
Thread finalizado! Retorno = Minha primeira Thread!

No segundo exemplo, a mesma rotina é utilizada. No entanto, dois threads são criados.

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

/*Rotina que será executada pelos dois threads*/
void * routine(void *arg);

int main (int argc, char *argv[])
{
	pthread_t thread_idA;
	pthread_t thread_idB;
	void * thread_res;
	int rstatus;
	
	/*tenta iniciar o thread, indicando a função 'routine' e passando como argumento a string "Thread A"*/
	rstatus = pthread_create (&thread_idA, NULL, routine, (void*)("Thread A"));

	/*verificar se ocorreu algum erro na chamada de pthread_create*/
	if(rstatus != 0)
	{
		printf ("Erro ao criar o thread A.\n");
		exit(EXIT_FAILURE);
	}

	printf ("Thread A criado com sucesso!\n");

	/*tenta iniciar o thread, indicando novamente a função 'routine' e passando como argumento a string "Thread B"*/
	rstatus = pthread_create (&thread_idB, NULL, routine, (void*)("Thread B"));

	/*verificar se ocorreu algum erro na chamada de pthread_create*/
	if(rstatus != 0)
	{
		printf ("Erro ao criar o thread B.\n");
		exit(EXIT_FAILURE);
	}

	printf ("Thread B criado com sucesso!\n");


	/*aguarda finalização do thread A identificado por thread_idA. O retorno é passado pelo ponteiro thread_res*/
	rstatus = pthread_join (thread_idA, &thread_res);

	if(rstatus != 0)
	{
		printf ("Erro ao aguardar finalização do thread A.\n");
		exit(EXIT_FAILURE);
	}

	printf ("Thread A finalizado! Retorno = %s\n", (char *)thread_res);


	/*aguarda finalização do thread B identificado por thread_idB. O retorno é passado pelo ponteiro thread_res*/
	rstatus = pthread_join (thread_idB, &thread_res);

	if(rstatus != 0)
	{
		printf ("Erro ao aguardar finalização do thread B.\n");
		exit(EXIT_FAILURE);
	}

	printf ("Thread B finalizado! Retorno = %s\n", (char *)thread_res);

	return 0;
}

void * routine(void *arg)
{
	int contador = 10;

	/*procedimento para decrementar um contador e exibir o seu valor*/
	while(contador--)
	{
		printf("%s: %i\n", (char *)arg, contador);
	}

	/*finaliza a função retornando o argumento que foi recebido*/
	pthread_exit(arg);
}

O resultado da execução do programa é mostrado abaixo. Nesse programa, devido ao procedimento simples executado pela rotina, o thread foi finalizado antes que pudesse ser trocado por outro.

Thread A criado com sucesso!
Thread B criado com sucesso!
Thread B: 9
Thread B: 8
Thread B: 7
Thread B: 6
Thread B: 5
Thread B: 4
Thread B: 3
Thread B: 2
Thread B: 1
Thread B: 0
Thread A: 9
Thread A: 8
Thread A: 7
Thread A: 6
Thread A: 5
Thread A: 4
Thread A: 3
Thread A: 2
Thread A: 1
Thread A: 0
Thread A finalizado! Retorno = Thread A
Thread B finalizado! Retorno = Thread B

Isso pode ser modificado alterando a rotina de execução, adicionando a chamada da função sched_yield. Como dito anteriormente, essa função causa a interrupção do thread. 

void * routine(void *arg)
{
	int contador = 10;

	/*procedimento para decrementar um contador e exibir o seu valor*/
	while(contador--)
	{
		printf("%s: %i\n", (char *)arg, contador);
		sched_yield();
	}

	/*finaliza a função retornando o argumento que foi recebido*/
	pthread_exit(arg);
}

O resultado da execução do programa é mostrado abaixo. Em comparação com o segundo caso, é possível observar que agora os threads tem sua execução alternada.

Thread A criado com sucesso!
Thread B criado com sucesso!
Thread B: 9
Thread A: 9
Thread B: 8
Thread A: 8
Thread B: 7
Thread A: 7
Thread B: 6
Thread A: 6
Thread B: 5
Thread A: 5
Thread B: 4
Thread A: 4
Thread B: 3
Thread A: 3
Thread B: 2
Thread A: 2
Thread B: 1
Thread A: 1
Thread B: 0
Thread A: 0
Thread A finalizado! Retorno = Thread A
Thread B finalizado! Retorno = Thread B

Conclusões parciais

Esse artigo teve como objeto fundamentar o conceito de threads e apresentar o padrão conhecido como POSIX Threads, também chamado de pthreads. No entanto, a aplicação desse recurso envolve outros aspectos que não foram abordados neste artigo. Pois, a partir do momento em que a aplicação é dividida em segmentos que são executados de forma alternada, e tais segmentos acessam a mesma informação, vem à tona questões voltadas ao compartilhamento de recursos e sincronização de operações. Essas características, se não forem tratadas corretamente, podem gerar uma grande confusão e, consequentemente, resultar em erro de execução de um programa.

Referências

Imagem destacada.

Notificações
Notificar
guest
1 Comentário
recentes
antigos mais votados
Inline Feedbacks
View all comments
zegotinha
zegotinha
24/10/2020 18:49

muito bom

Last edited 10 meses atrás by zegotinha

WEBINAR

Visão Computacional para a redução de erros em processos manuais

DATA: 23/09 ÀS 17:00 H