Ao executarmos um programa simples, o sistema operacional inicial cria um processo (uma instância de um programa em execução). Esse processo possui um espaço de endereçamento e, em sistemas tradicionais, apenas uma thread (segmento ou fluxo de controle) de execução que segue a sequência de instruções definidas na função main().
Uma thread é, portanto, um fluxo de execução que ocorre dentro do um processo.
Vamos supor um programa bem simples que tem como objetivo, imprimir na tela os números de 0 a 9 e as letras de A a Z:
// imprimir_numeros_letras_v01.c
#include <stdio.h>
#include <unistd.h> // para a função usleep()
int main() {
// Imprimindo os números
for(int i = 0; i <= 9; i++){
printf("%d", i);
fflush(stdout); // Força a saída imediata
usleep(100000); // 100ms = 0.1 segundos
}
// Imprimindo as letras de 'A' a 'Z'
for(char c = 'A'; c <= 'Z'; c++){
printf("%c", c);
fflush(stdout); // Força a saída imediata
usleep(100000); // 100ms = 0.1 segundos
}
return 0;
}
// Para compilar: gcc imprimir_numeros_letras_v01.c -o imprimir_numeros_letras_v01
// Para executar: ./imprimir_numeros_letras_v01
Ao executarmos esse programa, o processo criado terá um única thread que seguirá a ordem sequencial estabelecida na função main(), imprimindo primeiro os números e depois as letras.

Nos sistemas atuais, é possível que existam mais de uma thread em um processo. As várias threads compartilham o mesmo espaço de memória, mas executam de forma independente e paralela. Por exemplo, um programa que faz três tarefas demoradas (ler arquivo, baixar da internet, calcular algo) pode criar três threads, uma pra cada tarefa, e rodar todas ao mesmo tempo.
Para uma abordagem mais teórica sobre threads, clique aqui.
Voltando para o nosso programa inicial, vamos por meio de threads imprimir números e letras de forma paralela. Para isso a thread principal (criada junto ao processo, seguindo as instruções da função main()) ficará a cargo de imprimir os números de 0 a 9 e criaremos uma thread secundária que ficará a cargo de imprimir as letras.
Em sistemas POSIX (Linux, macOS, etc.), usamos a biblioteca <pthread.h>, que possui mais de 60 funções para criar e gerenciar threads. Essa biblioteca também define estruturas de dados e atributos que usualmente são passados como argumentos para os parâmetros das funções.
A função pthread_create() cria uma nova thread dentro de um processo.
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void * (*start_routine) (void *arg), void *arg)A função
pthread_createé utilizada para inicializar um thread. Para isso, a função recebe como argumento o endereço de um dadopthread_tque será inicializado. Além disso, o endereço da função que será executada é informado no parâmetrostart_routine. Tal função tem como retorno um valorvoid *e recebe apenas um argumento a partir do seu parâmetrovoid *. Esse argumento é passado para funçãostart_routineespecificando o último parâmetro da funçãopthread_create, denominadoarg. 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.
Vamos analisar o código a seguir:
// imprimir_numeros_letras_v02.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // para usleep()
void* thread_secundario(void* arg);
int main() {
// pthread_t é um tipo de dado para armazenar o valor do id do thread_secundario
pthread_t id_thread_secundario;
// Função que cria o thread_secundario
pthread_create(&id_thread_secundario, NULL, thread_secundario, NULL);
// Imprimindo os números
for(int i = 0; i <= 9; i++){
printf("%d", i);
fflush(stdout); // Força a saída imediata
usleep(100000); // 100ms = 0.1 segundos
}
return 0;
}
void* thread_secundario(void* arg){
// Imprimindo as letras de 'A' a 'Z'
for(char c = 'A'; c <= 'Z'; c++){
printf("%c", c);
fflush(stdout); // Força a saída imediata
usleep(100000); // 100ms = 0.1 segundos
}
}
// Para compilar: gcc imprimir_numeros_letras_v02.c -o imprimir_numeros_letras_v02
// Para executar: ./imprimir_numeros_letras_v02
Note que na função main(), primeiramente declaramos um variável id_thread_secundario do tipo pthread_t que será um dos argumentos passados por referência para a função pthread_create() . Nessa variável será armazenada um número que identifica o thread criado. Em seguida, chamamos a função pthread_create() , passando como argumento a função thread_secundario(). A partir daí o fluxo de execução do processo se divide em dois thread: o primeiro, é o thread principal (da função main()) que imprimirá os números de 0 a 9; já o segundo thread imprimirá as letras. Vejamos o resultado da execução desse código.

Note que a saída apresenta letras e números de forma intercalada e não mais sequencial. Isso ocorre, porque temos dois thread sendo processados em paralelo.
Contudo, note que a execução terminou sem que todas as letras fossem impressas. Os números de 0 a 9 foram impressas mas as letras pararam de ser impressas na letra ‘J’. Isso ocorreu porque o thread principal tinha uma tarefa menor para realizar, isto, é imprimir 10 caracteres numéricos, enquanto a thread secundária tinha que imprimir os 26 caracteres do alfabeto e quando a thread principal acabou o programa foi encerrado sem que a thread secundária tivesse finalizado. Para resolver esse problema, deveríamos usar a função pthread_join().
int pthread_join (pthread_t thread, void **thread_return);A função
pthread_joinrecebe como argumentos a estrutura de controle do thread, do tipopthread_t, que será finalizado e o endereço de um ponteiro (void **) para o valor de retorno do thread.
O código a seguir corrige esses problemas, utilizando a função pthread_join():
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // para usleep()
void* thread_secundario(void* arg);
int main() {
printf("Início da execução do programa...\n");
// pthread_t é um tipo de dado para armazenar o valor do id do thread
pthread_t id_thread_principal = pthread_self();
pthread_t id_thread_secundario;
printf("Antes da função 'pthread_create', apenas o thread principal existe: ID = %lu\n", id_thread_principal);
// Função que cria o thread_secundario
pthread_create(&id_thread_secundario, NULL, thread_secundario, NULL);
// Imprimindo os números
for(int i = 0; i <= 9; i++){
printf("%d", i);
fflush(stdout); // Força a saída imediata
usleep(100000); // 100ms = 0.1 segundos
}
printf("\nFim do thread principal. Continuando até o thread secundario acabar...\n");
pthread_join(id_thread_secundario, NULL);
printf("\nOs dois threads finalizaram. Fim da execução do programa\n");
return 0;
}
void* thread_secundario(void* arg){
printf("\nInício do thread secundario: ID = %lu\n", pthread_self());
// Imprimindo as letras de 'A' a 'Z'
for(char c = 'A'; c <= 'Z'; c++){
printf("%c", c);
fflush(stdout); // Força a saída imediata
usleep(100000); // 100ms = 0.1 segundos
}
}
