Threads
Last updated
Agora que entendemos como funciona o forking de processos, vamos explorar as threads. Threads são uma forma mais leve de concorrência, permitindo que múltiplas tarefas sejam executadas dentro do mesmo processo, compartilhando o mesmo espaço de memória.
Conforme explicado na primeira parte do guia, no sistema operacional podemos criar threads através da chamada de sistema clone com os argumentos corretos. Na linguagem C, conseguimos manipular threads do sistema operacional, também chamadas de kernel threads.
Entretanto em C, embora podemos, não precisamos chamar a syscall clone diretamente devido à sua complexidade inerente. Mas para nossa sorte, na biblioteca padrão temos acesso a um cabeçalho chamado pthread.h, que abstrai de forma muito mais simples a criação de kernel threads utilizando POSIX Threads (ou pthreads), que são o padrão em sistemas UNIX-like:
A função pthread_create recebe:
um ponteiro para a variável que irá referenciar a thread na memória (pthread_t)
um ponteiro para a função que será executada no contexto da thread
outros argumentos opcionais, que iremos deixar como NULL
Ao executar por algumas vezes o programa, podemos perceber a inconsistência nas mensagens, muitas vezes imprimindo apenas:
Bom, se você leu direitinho a primeira parte do guia, vai se lembrar que:
Todo programa tem uma thread principal
Ou seja, quando o processo é iniciado, ele é encapsulado dentro de uma thread chamada "principal", pelo que quando falamos do processo em si, estamos também falando desta thread principal.
Entretanto, podemos ver que a mensagem da thread criada com pthread_create
não apareceu na saída, e isto se deve à natureza de concorrência do escalonamento de tarefas do sistema operacional, onde verificamos em ação no tópico anterior com forking de processos.
Se eu rodar o programa, pode ser que a thread foi escalonada rapidamente e a mensagem aparece com sucesso. Mas pode ser também que a thread ainda não foi escalonada e o programa principal já foi finalizado. Enfim:
Não temos controle algum sobre a ordem e execução das tarefas no sistema operacional!
Como fazer com que o programa principal "espere" uma ou mais threads em execução serem finalizadas?
Com a função pthread_join
, o contexto da thread será trazido para o mesmo contexto da thread principal, então na prática o programa irá esperar pela execução da thread até que ela seja finalizada:
Saída:
Wow! How cool is that?
Já vimos os building blocks para criação de threads em C. Agora, vamos a um exemplo um pouco mais robusto, similar ao que aprendemos no tópico de forking de processos:
Repare que a ordem de execução pode mudar, devido à (e lá vamos novamente repetir) natureza da concorrência.
Até o momento, exploramos 2 formas de concorrência em C que são forking de processos e threads.
No forking, a comunicação entre processos (IPC) precisa ser feita através de pipes ou forçar algum mecanismo de compartilhamento de memória, pois por padrão os processos não compartilham memória uns com os outros.
Ja com threads, cada thread compartilha o mesmo contexto que é a memória do processo principal, ou seja, duas ou mais threads podem provocar um cenário de condição de corrida se precisarem ler/escrever no mesmo recurso.
Para mitigar problemas de acesso a recurso compartilhado entre threads, precisamos recorrer ao uso de locks.