Domine o Multithreading em C++: Programação Concorrente Eficiente com Pthreads
Multithreading é uma forma especializada de multitarefa. Em geral, existem dois tipos: baseada em processos e baseada em threads. A multitarefa baseada em processos lida com a execução concorrente de programas distintos. A baseada em threads lida com a execução concorrente de partes do mesmo programa.
Um programa multithread contém duas ou mais partes que podem ser executadas concorrentemente. Cada parte é chamada de thread (linha de execução) e define um caminho de execução separado.
O C++ não possui suporte nativo para aplicações multithread até o padrão C++11. Antes disso, dependia-se inteiramente do sistema operacional. Este tutorial assume Linux e usa POSIX Threads (Pthreads), API disponível em sistemas Unix-like como FreeBSD, NetBSD, GNU/Linux, macOS e Solaris.
Criando Threads
A rotina a seguir cria uma thread POSIX:
#include <pthread.h>
pthread_create(thread, attr, start_routine, arg)
A função pthread_create cria uma nova thread e a torna executável. Pode ser chamada quantas vezes forem necessárias. Parâmetros:
| Nº | Parâmetro e Descrição |
|---|---|
| 1 | thread Identificador opaco e único para a nova thread retornado pela sub-rotina. |
| 2 | attr Objeto de atributo opaco para definir atributos da thread. Use NULL para valores padrão. |
| 3 | start_routine Rotina C++ que a thread executará após ser criada. |
| 4 | arg Argumento único passado para start_routine. Deve ser passado por referência como ponteiro void. Use NULL se nenhum argumento for necessário. |
O número máximo de threads é dependente da implementação. Uma vez criadas, as threads são pares e podem criar outras threads. Não há hierarquia ou dependência implícita.
Encerrando Threads
Use a rotina a seguir para encerrar uma thread POSIX:
#include <pthread.h>
pthread_exit(status)
pthread_exit é usado para sair explicitamente de uma thread. Geralmente chamado após a thread completar seu trabalho.
Se main() terminar antes das threads criadas e sair com pthread_exit(), as outras threads continuarão executando. Caso contrário, serão terminadas automaticamente quando main() finalizar.
Exemplo: Criando 5 Threads
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid = (long)threadid;
cout << "Hello World! Thread ID, " << tid << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int rc, i;
for (i = 0; i < NUM_THREADS; i++) {
cout << "main(): criando thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
if (rc) {
cout << "Erro: incapaz de criar thread, " << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Compile com a biblioteca -lpthread:
$ g++ test.cpp -lpthread
Saída esperada (a ordem pode variar):
main(): criando thread, 0
main(): criando thread, 1
main(): criando thread, 2
main(): criando thread, 3
main(): criando thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4
Passando Argumentos para Threads
Este exemplo mostra como passar múltiplos argumentos via estrutura. Qualquer tipo de dado pode ser passado no callback da thread, pois ele aponta para void.
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data {
int thread_id;
char *message;
};
void *PrintHello(void *threadarg) {
struct thread_data *my_data = (struct thread_data *) threadarg;
cout << "Thread ID: " << my_data->thread_id;
cout << " Mensagem: " << my_data->message << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc, i;
for (i = 0; i < NUM_THREADS; i++) {
cout << "main(): criando thread, " << i << endl;
td[i].thread_id = i;
td[i].message = "Esta é a mensagem";
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
if (rc) {
cout << "Erro: incapaz de criar thread, " << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Unindo e Desanexando Threads
Duas rotinas para juntar ou desanexar threads:
pthread_join(threadid, status)
pthread_detach(threadid)
pthread_join() bloqueia a thread chamadora até que a threadid especificada termine. Apenas threads criadas como joinable podem ser unidas. Threads criadas como detached nunca podem ser unidas.
Exemplo: Aguardando Conclusão com pthread_join
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t) {
long tid = (long)t;
sleep(1);
cout << "Dormindo na thread " << endl;
cout << "Thread com id: " << tid << " ...saindo " << endl;
pthread_exit(NULL);
}
int main() {
int rc, i;
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
// Inicializa e define thread como joinable
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (i = 0; i < NUM_THREADS; i++) {
cout << "main(): criando thread, " << i << endl;
rc = pthread_create(&threads[i], &attr, wait, (void *)i);
if (rc) {
cout << "Erro: incapaz de criar thread, " << rc << endl;
exit(-1);
}
}
// Libera atributo e aguarda as outras threads
pthread_attr_destroy(&attr);
for (i = 0; i < NUM_THREADS; i++) {
rc = pthread_join(threads[i], &status);
if (rc) {
cout << "Erro: incapaz de unir, " << rc << endl;
exit(-1);
}
cout << "Main: thread id concluída: " << i;
cout << " saindo com status: " << status << endl;
}
cout << "Main: programa saindo." << endl;
pthread_exit(NULL);
}
Saída esperada:
main(): criando thread, 0
main(): criando thread, 1
main(): criando thread, 2
main(): criando thread, 3
main(): criando thread, 4
Dormindo na thread
Thread com id: 0 ...saindo
Dormindo na thread
Thread com id: 1 ...saindo
Dormindo na thread
Thread com id: 2 ...saindo
Dormindo na thread
Thread com id: 3 ...saindo
Dormindo na thread
Thread com id: 4 ...saindo
Main: thread id concluída:0 saindo com status:0
Main: thread id concluída:1 saindo com status:0
Main: thread id concluída:2 saindo com status:0
Main: thread id concluída:3 saindo com status:0
Main: thread id concluída:4 saindo com status:0
Main: programa saindo.
Com Pthreads, você tem controle total sobre o ciclo de vida das threads, permitindo construir aplicações concorrentes robustas e eficientes em C++.
Linguagem C
- Dominando Operadores em C++: Guia Completo com Exemplos Práticos
- Comentários em C++: Boas Práticas para Código Legível e Manutenível
- Dominando Templates de Classe em C++: Um Guia Prático
- Linguagem de Programação C++: Visão Geral e Principais Características
- Constantes e Literais em C++: Tipos, Sintaxe e Boas Práticas
- Guia Completo de Operadores em C++: Tipos, Uso e Boas Práticas
- Dominando Tipos Numéricos em C++: Guia Prático
- Entendendo Referências em C++: Conceitos-Chave e Diferenças de Ponteiros
- Dominando Templates em C++: Guia para Programação Genérica
- Domine o Multithreading em C++: Programação Concorrente Eficiente com Pthreads