Threads para Raiz – Parte 2 – Programação C

Neste segundo artigo, utilizaremos o exemplo apresentado e iremos desenvolver nossa aplicação em C para linux.

Github

Para ilustrar nosso exemplo criei uma pasta C no nosso projeto git.

Threads para Raiz – Parte 1

Todos os itens deste artigos estão na pasta C.

Makefile

Para quem acompanha meu canal, o nosso bem amado Makefile é o contrutor da aplicação.

PROGRAMA=threads
PROGRAMA32=threads32

LIBS= -lpthread

CC=gcc

SOURCE= threads.c

all32: clean compile32


all: clean compile

clean:
	rm -f *.o
	rm -f $(PROGRAMA)

compile32:
	$(CC) -m32  $(SOURCE) $(LIBS) -o $(PROGRAMA32)

compile:
	$(CC) $(SOURCE) $(LIBS) -o $(PROGRAMA)

Aqui criamos um construtor para ambas as arquituras 32 e 64bits.

Por padrão iremos manter o uso em 64bits que é a plataforma nativa do meu linux.

Criando a base

O primeiro passo é criar a base do nosso chamador.

Para tanto iremos no main, inicializar as variáveis e chamar os chamadores das threads.

void main(void){
	int rstatus = 0;
	srand(time(NULL));
	pthread_t  pidRecepcao = 0;
	pthread_t  pidControlador = 0;
	pthread_t  pidExecutor = 0;
	//mutex = PTHREAD_MUTEX_INITIALIZER;

	printf("\nBem vindo ao programa das threads\n");
	printf("Este programa faz parte do artigo:\n");
	printf("http://maurinsoft.com.br/index.php/2022/07/02/threads-para-raiz-parte-1/\n\n");
	printf("Inicializando vetor\n\n");
	StartVetor();
	printf("Iniciando Recepcao\n");
	pidRecepcao = Start_Recepcao();
	printf("Iniciando Controlador\n");
	pidControlador = Start_Controlador();
	printf("Iniciando Executor\n");
	pidExecutor = Start_Executor();
	//bool flag = ((pidRecepcao!=0)||(pidControlador!=0)||(pidExecutor!=0));
	flgTerminou = false;
	while(!flgTerminou){
		printf("Status:");

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

		}

		flgTerminou = flgTerminouRecepcao && flgTerminouExecucao;
		sleep(1);
	}
	printf("\n\nFila ordenada:");
	for(int cont = 0;cont<=MAXITEMS-1;cont++)
	{
	  printf("%i ",fila[cont]);
	}
	printf("\n\n");

	printf("\n\nFila executada:");
	for(int cont = 0;cont<=MAXITEMS-1;cont++)
	{
	  printf("%i ",executada[cont]);
	}
	printf("\n\n");

}

As funcoes Start_Recepcao, Start_Controlador, Start_Executor chamam as threads, que serão apresentadas em um próximo momento.

A variavel pidRecepcao , do tipo pthread_t que indica cada uma das threads criadas.

Inicialização de variáveis

A inicialização da fila é feita pela função StartVetor, conforme fonte abaixo:

void StartVetor(){
		for (int cont = 0;cont<=MAXITEMS;cont++)
		{
			fila[cont] = 0;
			executada[cont]=0;

		}
}

Temos também a variável executada, que será utilizada na marcação dos itens executados.

Sessão Crítica

O controle da sessão crítica é feita nas funções IniciaSessaoCritica e TerminaSessaoCritica, conforme fonte abaixo:


int IniciaSessaoCritica()
{
	int  rc;
	int cont = 0;
	while ( (rc = pthread_mutex_lock(&mutex))!=0)
	{
		usleep(100); /*Aguarda um pouco*/
		cont++;
		printf("Sessao critica não conseguida\n");
		if (cont>3) break;
	}
	return rc;


}

int TerminaSessaoCritica()
{
	int  rc;
	int cont = 0;
	while ( (rc = pthread_mutex_unlock(&mutex))!=0)
	{
		usleep(100); /*Aguarda um pouco*/
		cont++;
		printf("Sessao critica não liberada\n");
		if (cont>3) break;
	}
	return rc;


}

Ambas as funções controlam a sessão criada mutex, declarada, conforme fonte abaixo:

pthread_mutex_t    mutex = PTHREAD_MUTEX_INITIALIZER;

Para cada sessão critica, deve-se criar uma variável de controle. Como usaremos apenas a fila, como variável de troca de dados, utilizaremos uma única sessão critica.

Criando a thread da recepção

Agora iremos dar inicio a criação da thread da recepção, na qual incluiremos o inicio da thread.

pthread_t  Start_Recepcao()
{
	pthread_t  pid	= 0;
	int ret;
	ret=pthread_create(&pid,NULL,&threadRecepcao,NULL);

	return pid;
}

Neste segmento do código, podemos acompanhar que criamos a thread com pthread_create, passando o ponteiro da função que será criada a thread threadRecepcao. Desta forma a função threadRecepcao, não mais seguirá na thread pai. O ultimo parâmetro é utilizado para passagens de argumentos, no nosso caso, não será usado.

Por fim a função da thread

/*thread function definition*/
void* threadRecepcao(void* args)
{
	for(int cont = 0;cont<=MAXITEMS;cont++)
	{

		printf("Criando pacote nro %d\n",cont);
		if(IniciaSessaoCritica()==0)
		{
			int Valor = (rand()% 100);
			fila[cont] = Valor; /*Grava Valor na fila*/
			printf("Registrou na fila[%d] = %d\n",cont,Valor);
			TerminaSessaoCritica();
			usleep(1000);
		} else {
				printf("Falha na Recepcao nro:\n",cont);
		}

	}
	printf("Recepcao terminou atendimento\n");

}

Podemos observar que a threadRecepção requisita a sessão crítica para movimentar a fila, apenas depois alocando valor para ela. Também podemos perceber que após seu uso, o mesmo é descartado.

Criando o Controlador

O controlador, é executado, em seguida, ordenando as informações.

void ordenacao()
{
int i, x;
bool flgordenado = false;
while((!flgordenado)&&(!flgTerminou))
{
	//flgordenado = true;
	for (i=0; i<=MAXITEMS-1; i++)
	{
		if ((fila[i]!=0)&&(fila[i+1]!=0))
		{
		  if(fila[i]>fila[i+1])
		  {
		    x = fila[i];
            fila[i] = fila[i+1];
		    fila[i+1] = x;
		    flgordenado = false;
		    //printf("Ordenando os pacote nro %d\n",i);
		  }
		}	else {
			//printf("Posicao vazia %d\n",i);
			flgordenado = false;
		}
    }


  }
}

/*thread function definition*/
void* threadControlador(void* args)
{
  int oldValue, newValue;
  int flgOrdenado = false;
  printf("Iniciou o controlador\n");
  while(!flgTerminou) /*Faz enquanto nao terminar e nao ordenao*/
  {
	if(IniciaSessaoCritica()==0)
	{
		ordenacao();

		TerminaSessaoCritica();
		//usleep(200);

	}
  }

  printf("Terminou ordenação de todos os itens\n");
}

Neste bloco temos duas funções:

A thread em si é a threadControlador, que chama a função de ordenação ordenacao.

A ordenação só para quando for atendida duas condições:

Tiver terminado as demais threads, indicado pelo flag flgTerminou e quando tiver sido totalmente ordenado flgordenado.

Executor

A thread do Executor, pode ser vista conforme apresentado a seguir:

/*thread function definition*/
void* threadExecutor(void* args)
{
  int oldValue, newValue;
  bool flgExecutado = false;
  printf("Iniciou o controlador\n");
  int posicao = 0;
  //while(!flgExecutado)
  while((!flgExecutado)&&(!flgTerminou))
  {
    //printf("Pesquisando Posicao %d\n",posicao);

	//printf("Entrou na sessao critica\n");
	if  (fila[posicao]==0) /*Fila nao foi preenchida*/
	{
		printf("posicao vazia %d\n",posicao);
		posicao = posicao;

	} else
	{
	   executada[posicao] = fila[posicao];
	   printf("Executou[%d] = %d\n",posicao,fila[posicao]);
	   if(posicao==MAXITEMS-1)
	   {
		   printf("Chegou ao fim\n ");
		   flgExecutado= true; /*Finaliza executor*/
	   } else
	   {
		   posicao ++;
	   }

	}
	//printf("terminou while\n");
  }

  flgTerminouExecucao = true;

  printf("Terminou ordenação de todos os itens\n");

}

Ela irá rodar até que duas condições sejam satisfeitas:

  • flgExecutado – Controla a execução de todas as tarefas da lista
  • flgTerminou – Controla o fim de todas as demais threads do sistema

Testando programa

Compilando programa

A compilação em um Raspberry PI ocorreu com sucesso.

Executando o programa

root@raspberrypi:/home/mmm/projetos/Threads-para-Raiz/c# ./threads

Bem vindo ao programa das threads
Este programa faz parte do artigo:
http://maurinsoft.com.br/index.php/2022/07/02/threads-para-raiz-parte-1/

Inicializando vetor

Iniciando Recepcao
Iniciando Controlador
Registrou na fila[0] = 80
Registrou na fila[1] = 11
Registrou na fila[2] = 76
Registrou na fila[3] = 71
Registrou na fila[4] = 27
Registrou na fila[5] = 23
Registrou na fila[6] = 33
Registrou na fila[7] = 68
Registrou na fila[8] = 32
Registrou na fila[9] = 92
Recepcao terminou atendimento
Iniciou o controlador
Iniciando Executor
Status:Iniciou o controlador
Executou[0] = 11
Executou[1] = 23
Executou[2] = 27
Executou[3] = 32
Executou[4] = 33
Executou[5] = 68
Executou[6] = 71
Executou[7] = 76
Executou[8] = 80
Executou[9] = 92
Chegou ao fim
 Terminou ordenação de todos os itens
Status:Terminou ordenação de todos os itens


Fila ordenada:11 23 27 32 33 68 71 76 80 92



Fila executada:11 23 27 32 33 68 71 76 80 92

Como a execução ficou um pouco longa, resolvi jogar como código.

Foto da execução

Conclusão

Podemos perceber que a execução de threads pode ser facilitada com uso de flags externos, que facilitam a comunicação entre os processos. Pudemos ver tambem, que a programação em threads é de fato, um estado de arte da programação C, pois envolve alem de um algoritmo bem desenvolvido, a elaboração de estratégias pensando em que uma thread não sabe exatamente quando a outra irá cumprir suas atividades.

Desta forma cada thread tem que ser pensada em aguardar e esperar as informações das threads que colaboraram com esta.

Desta forma o controle interno das informações é bem mais complicado.

Pudemos ver por ultimo o uso dos sinalizadores, que visão identificar o uso de uma sessão crítica do sistema. Que nada mais é que um recurso compartilhado.

Espero que tenham gostado do artigo.

Em caso de dúvidas ou sugestões, fico a disposição como sempre.

marcelomaurinmartins@gmail.com

Bibliografia