C++: Noções Básicas de Multithreading
Última atualização: 2026-08-26
Na aula 42, aprendemos sobre expressões regulares.
Agora, vamos entrar no território avançado do C++ — programação multithreaded.
Computadores modernos são todos multi-core, mas programas single-threaded só podem usar um core — que desperdício.
Multithreading permite que você faça múltiplas coisas ao mesmo tempo, melhorando drasticamente o desempenho.
1. Visão Geral de Multithreading
(1) 1.1 O Que É uma Thread?
Uma thread é a menor unidade de execução de um programa.
Processo vs Thread:
- Processo: Unidade de alocação de recursos (espaço de memória independente)
- Thread: Unidade de execução (comparte a memória do processo)
Analogia do mundo real:
- Processo = Fábrica
- Thread = Trabalhador (múltiplos trabalhadores compartilham os recursos da fábrica)
(2) 1.2 Por Que Usar Multithreading?
| Vantagem | Descrição |
|---|---|
| Desempenho melhorado | Computação paralela multi-core |
| Maior responsividade | Thread de UI não bloqueia |
| Design simplificado | Atribuir tarefas diferentes a threads diferentes |
2. Criando Threads
(1) 2.1 Uso Básico
O C++11 fornece a classe std::thread no header thread.
Exemplo: Criando uma thread (Dificuldade ⭐)
▶ Exemplo 1: Demo de programação multithreading (Dificuldade ⭐)
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(hello); // Criar thread
t.join(); // Esperar a thread terminar
std::cout << "Main thread ends" << std::endl;
return 0;
}
Saída:
Main thread: starting
Main thread: waiting for child thread
Child thread: Hello from thread
Main thread: ended
Resultado da execução:
Hello from thread!
Main thread ends
(2) 2.2 join vs detach
| Função | Propósito | Descrição |
|---|---|---|
join() |
Esperar a thread terminar | Bloqueia a thread atual |
detach() |
Desanexar thread | Thread executa independentemente, não pode mais ser joined |
Exemplo: Esperando com join (Dificuldade ⭐)
#include <iostream>
### ▶ Exemplo 2: Demo de programação multithreading (Dificuldade ⭐)
#include <thread>
#include <chrono>
void worker(int id) {
for (int i = 0; i < 3; i++) {
std::cout << "Worker " << id << " working..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join(); // Esperar t1 terminar
t2.join(); // Esperar t2 terminar
std::cout << "All workers done" << std::endl;
return 0;
}
3. Passando Argumentos para Threads
(1) 3.1 Passando Parâmetros
O construtor std::thread pode aceitar qualquer objeto chamável e argumentos.
Exemplo: Passando parâmetros (Dificuldade ⭐⭐)
#include <iostream>
#include <thread>
#include <string>
void printMessage(std::string msg, int count) {
for (int i = 0; i < count; i++) {
std::cout << msg << std::endl;
}
}
int main() {
std::thread t(printMessage, "Hello", 3);
t.join();
return 0;
}
(2) 3.2 Passagem por Referência
Por padrão, argumentos são passados por valor. Para passar por referência, você deve usar std::ref.
Exemplo: Passagem por referência (Dificuldade ⭐⭐)
#include <iostream>
#include <thread>
#include <functional>
void increment(int& x) {
x++;
}
int main() {
int counter = 0;
std::thread t(increment, std::ref(counter));
t.join();
std::cout << "Counter: " << counter << std::endl; // Saída: 1
return 0;
}
4. Mutexes
(1) 4.1 Por Que Precisamos de Mutexes?
Problema: Múltiplas threads acessando dados compartilhados simultaneamente causa condições de corrida.
Exemplo: Condição de corrida (Dificuldade ⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
int counter = 0;
void increment() {
for (int i = 0; i < 1000; i++) {
counter++; // Múltiplas threads modificando simultaneamente, resultado é incerto
}
}
int main() {
std::vectorstd::thread threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // Esperado 10000, real pode ser menor
return 0;
}
(2) 4.2 Protegendo Dados Compartilhados com um Mutex
Um mutex (Mutex) garante que apenas uma thread acesse dados compartilhados por vez.
Exemplo: Protegendo com mutex (Dificuldade ⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000; i++) {
mtx.lock(); // Bloquear
counter++;
mtx.unlock(); // Desbloquear
}
}
int main() {
std::vectorstd::thread threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // Sempre 10000
return 0;
}
(3) 4.3 lock_guard — Estilo RAII
Recomendado: Use std::lock_guard para bloqueio/desbloqueio automático.
void increment() {
for (int i = 0; i < 1000; i++) {
std::lock_guardstd::mutex lock(mtx); // Bloqueia na construção, desbloqueia na destruição
counter++;
} // Desbloqueia automaticamente
}
5. Variáveis de Condição
(1) 5.1 Por Que Precisamos de Variáveis de Condição?
Problema: Uma thread precisa esperar que uma condição se torne verdadeira (ex.: fila não está vazia).
Solução: std::condition_variable
(2) 5.2 Exemplo: Produtor-Consumidor (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
std::queueint q;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 10; i++) {
std::lock_guardstd::mutex lock(mtx);
q.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_one(); // Notificar consumidor
}
}
void consumer() {
for (int i = 0; i < 10; i++) {
std::unique_lockstd::mutex lock(mtx);
cv.wait(lock, { return !q.empty(); }); // Esperar até a fila não estar vazia
int value = q.front();
q.pop();
std::cout << "Consumed: " << value << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
Saída:
Produced:
Consumed:
6. Tarefas Assíncronas
(1) 6.1 std::async
std::async é usado para lançar uma tarefa assíncrona e retorna um std::future.
Exemplo: Computação assíncrona (Dificuldade ⭐⭐)
#include <iostream>
#include <future>
int calculate(int x) {
return x * x;
}
int main() {
std::futureint result = std::async(calculate, 10);
std::cout << "Result: " << result.get() << std::endl; // Saída: 100
return 0;
}
❓ Perguntas Frequentes
P: Quantas threads devo usar? R: Tipicamente igual ao número de cores da CPU. Threads demais causam overhead de troca de contexto.
P: O que é um deadlock? R: Duas threads esperando uma pela outra liberar locks, então nenhuma pode continuar.
Como evitar:
- Bloqueie em uma ordem fixa
- Use
std::lock()para bloquear múltiplos mutexes simultaneamente - Use
std::scoped_lock(C++17)
P: std::thread ou OpenMP — qual é melhor? R: -
std::thread: Flexível, multiplataforma - OpenMP: Simples, bom para computação científica
▶ Exemplo 3: Criando threads (Dificuldade ⭐)
#include <iostream>
#include <thread>
void printNumbers(int start, int end) {
for (int i = start; i <= end; i++) {
std::cout << i << " ";
}
std::cout << std::endl;
}
int main() {
std::thread t1(printNumbers, 1, 5);
std::thread t2(printNumbers, 10, 15);
t1.join();
t2.join();
return 0;
}
Saída:
Exemplo de saída:
1 10 2 11 3 12 4 13 5 14 15
std::thread cria threads, join() espera que terminem. A ordem da saída pode variar devido ao escalonamento de threads.
| Ponto-Chave | Resumo |
|---|---|
| std::thread | Criar threads |
| join/detach | Esperar/desanexar threads |
| std::mutex | Mutex, protege dados compartilhados |
| std::lock_guard | Bloqueio estilo RAII |
| std::condition_variable | Variável de condição, comunicação entre threads |
| std::async | Tarefas assíncronas |
📖 Resumo
- std::thread: Criar threads
- join(): Esperar a thread terminar
- detach(): Desanexar thread
- Funções de thread: Podem passar ponteiros de função, lambdas ou objetos de função
📝 Exercícios
-
Básico (Dificuldade ⭐): Crie duas threads que exibam "Thread A" e "Thread B" respectivamente, e observe a aleatoriedade da ordem de saída.
-
Intermediário (Dificuldade ⭐⭐): Crie 4 threads, cada uma computando uma soma acumulada sobre um intervalo de números (ex.: 1-2500, 2501-5000...), depois combine os resultados no final.
-
Desafio (Dificuldade ⭐⭐⭐): Use
std::asyncestd::futurepara implementar um simulador de download concorrente: crie 3 tarefas assíncronas, cada uma simulando o download de um arquivo de tamanho diferente, e espere todas terminarem antes de resumir.
- std::thread cria threads passando objetos chamáveis
- join espera a thread terminar, detach desanexa a thread
- Variáveis globais compartilhadas entre threads requerem sincronização
- std::this_thread::sleep_for coloca threads para dormir
- O número de threads não deve exceder o suporte de hardware (hardware_concurrency)
Próxima aula: Sincronização de Multithreading (#44)