Como criar um thread Linux em C
Usando a biblioteca pthread, você pode realizar gerenciamento de encadeamento de baixo nível para programação de alto desempenho.
No Linux, você pode criar e gerenciar encadeamentos em C/C++ usando a biblioteca de encadeamento POSIX (pthread). Ao contrário de outros sistemas operacionais, há pouca diferença entre um thread e um processo no Linux. É por isso que o Linux geralmente se refere a seus threads como processos leves.
Usando a biblioteca pthread, você pode criar threads, esperar que eles terminem e finalizá-los explicitamente.
A história do uso de threads no Linux
Antes do Linux versão 2.6, a implementação do encadeamento principal era LinuxThreads. Esta implementação teve limitações significativas em termos de desempenho e operações de sincronização. Um limite no número máximo de threads que poderiam ser executados os restringia a 1000s.
Em 2003, uma equipe liderada por desenvolvedores da IBM e da RedHat conseguiu disponibilizar o projeto Native POSIX Thread Library (NPTL). Foi introduzido pela primeira vez no RedHat Enterprise versão 3 para resolver problemas de desempenho com a Java Virtual Machine no Linux. Hoje, a biblioteca GNU C contém implementações de ambos os mecanismos de encadeamento.
Nenhuma delas é uma implementação de threads verdes, que uma máquina virtual gerenciaria e executaria em modo puramente de usuário. Quando você usa a biblioteca pthread, o kernel cria um thread toda vez que um programa é iniciado.
Você pode encontrar informações específicas do thread para qualquer processo em execução nos arquivos em /proc/
Lógica de Trabalho de Threads
Threads são como processos atualmente em execução no sistema operacional. Em sistemas de processador único (por exemplo, microcontroladores), o kernel do sistema operacional simula threads. Isso permite que as transações sejam executadas simultaneamente por meio do fatiamento.
Um sistema operacional single-core só pode realmente executar um processo por vez. No entanto, em sistemas com vários núcleos ou vários processadores, esses processos podem ser executados simultaneamente.
Criação de Threads em C
Você pode usar a função pthread_create para criar um novo thread. O arquivo de cabeçalho pthread.h inclui sua definição de assinatura juntamente com outras funções relacionadas ao encadeamento. Os threads usam o mesmo espaço de endereço e descritores de arquivo do programa principal.
A biblioteca pthread também inclui o suporte necessário para mutex e operações condicionais necessárias para operações de sincronização.
Ao usar as funções da biblioteca pthread, você deve garantir que o compilador vincule a biblioteca pthread ao seu executável. Se necessário, você pode instruir o compilador a vincular à biblioteca usando a opção -l:
gcc -o test test_thread.c -lpthread
A função pthread_create tem a seguinte assinatura:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
Ele retorna 0 se o procedimento for bem-sucedido. Se houver um problema, ele retornará um código de erro diferente de zero. Na assinatura da função acima:
-
O parâmetro thread é do tipo pthread_t. O thread criado estará sempre acessível com esta referência.
O parâmetro attr permite especificar um comportamento personalizado. Você pode usar uma série de funções específicas de encadeamento começando com pthread_attr_ para configurar esse valor. As personalizações possíveis são a política de agendamento, o tamanho da pilha e a política de desanexação.
start_routine especifica a função que o encadeamento executará.
arg representa uma estrutura de dados genérica passada para a função pelo thread.
Aqui está um exemplo de aplicação:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *worker(void *data)
{
char *name = (char*)data;
for (int i = 0; i < 120; i++)
{
usleep(50000);
printf("Hi from thread name = %s\n", name);
}
printf("Thread %s done!\n", name);
return NULL;
}
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("Exiting from main program\n");
return 0;
}
Tipos de rosca
Quando um thread retorna da função main() em um aplicativo, todos os threads são encerrados e o sistema libera todos os recursos usados pelo programa. Da mesma forma, ao sair de qualquer encadeamento com um comando como exit(), seu programa encerrará todos os encadeamentos.
Com a função pthread_join, você pode esperar que um encadeamento termine. O thread que usa essa função será bloqueado até que o thread esperado seja encerrado. Os recursos que utilizam do sistema não são devolvidos mesmo em casos como o encerramento de threads joinable, não escalonados pela CPU, ou mesmo falha ao ingressar com ptread_join.
Às vezes, há situações em que unir com pthread_join não faz sentido; se for impossível prever quando o thread terminará, por exemplo. Nesse caso, você pode garantir que o sistema retorne todos os recursos automaticamente no ponto em que o thread retorna.
Para conseguir isso, você deve iniciar os tópicos relevantes com o status DETACHED. Ao iniciar um encadeamento, o status DETACH pode ser definido por meio de valores de atributo de encadeamento ou com a função pthread_detach:
int pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int pthread_detach(pthread_t thread);
Aqui está um exemplo de uso de pthread_join(). Substitua a função principal no primeiro programa pelo seguinte:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("exiting from main program\n");
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}
Quando você compilar e executar o programa, sua saída será:
Hi from thread Y
Hi from thread X
Hi from thread Y
...
Hi from thread Y
exiting from main program
Hi from thread X
...
Hi from thread X
Thread X done!
Hi from thread Y
Thread Y done!
Término do Tópico
Você pode cancelar um thread com uma chamada para pthread_cancel, passando o id pthread_t correspondente:
int pthread_cancel(pthread_t thread);
Você pode ver isso em ação no código a seguir. Novamente, apenas a função principal é diferente:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(1);
printf("====> Cancelling Thread Y!!\n");
pthread_cancel(th2);
usleep(100000);
printf("====> Cancelling Thread X!\n");
pthread_cancel(th1);
printf("exiting from main program\n");
return 0;
}
Por que os tópicos são criados?
Os sistemas operacionais sempre tentam executar threads em uma ou mais CPUs, seja de uma lista autocriada ou de uma lista de threads criada pelo usuário. Alguns threads não podem ser executados porque estão aguardando um sinal de entrada/saída do hardware. Eles também podem estar esperando voluntariamente, aguardando uma resposta de outro encadeamento, ou ter outro encadeamento bloqueando-os.
Você pode ajustar os recursos que aloca aos encadeamentos criados usando pthread. Isso pode ser uma política de agendamento personalizada ou você pode escolher algoritmos de agendamento, como FIFO ou Round-robin, se desejar.