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.
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.
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
- https://www.embarcados.com.br/threads-posix/
- https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html
- https://www.ibm.com/docs/en/i/7.4?topic=ssw_ibm_i_74/apis/users_76.htm
- http://www.mathcs.emory.edu/~cheung/Courses/561/Syllabus/91-pthreads/intro-threads.html
- http://linguagemc.com.br/valores-aleatorios-em-c-com-a-funcao-rand/
- https://edisciplinas.usp.br/pluginfile.php/3061851/mod_resource/content/2/36-Mutex-v27.pdf
- https://www.treinaweb.com.br/blog/conheca-os-principais-algoritmos-de-ordenacao/