Última atualização:2026-10-12
Introdução a multi-thread em C++¶
Todo o código deste artigo está no repositório de C++那些事.
1.Entendendo threads e processos¶
O thread é a unidade básica de escalonamento; o processo é a unidade básica de alocação de recursos. Um programa pode ser entendido como um processo, e o processo contém vários threads.
Por exemplo: o navegador é um processo, e cada aba aberta é um thread.
Diferenças simples entre os dois:
- Espaço de endereçamento e outros recursos: processos são independentes entre si, enquanto os threads de um mesmo processo os compartilham. Os threads de um processo não são visíveis para outros processos.
- Comunicação: processos se comunicam via IPC; threads podem ler e escrever diretamente no segmento de dados do processo (como variáveis globais) para se comunicar — com o auxílio de mecanismos de sincronização e exclusão mútua, para garantir a consistência dos dados.
- Escalonamento e troca de contexto: a troca de contexto entre threads é muito mais rápida que a troca de contexto entre processos.
- Em um SO multi-thread, o processo não é uma entidade executável.
Quanto à comunicação via IPC e à comunicação entre threads, haverá um novo artigo depois.
2.Cinco maneiras de criar threads¶
- Ponteiro de função
- Função Lambda
- Functor (functor)
- Função-membro não estática
- Função-membro estática
2.1 Ponteiro de função¶
// 1.函数指针
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
// 调用
std::thread t1(fun, 10);
t1.join();
2.2 Função Lambda¶
// 注意:如果我们创建多线程 并不会保证哪一个先开始
int main() {
// 2.Lambda函数
auto fun = [](int x) {
while (x-- > 0) {
cout << x << endl;
}
};
// std::1.thread t1(fun, 10);
// 也可以写成下面:
std::thread t1_1([](int x) {
while (x-- > 0) {
cout << x << endl;
}
}, 11);
// std::1.thread t2(fun, 10);
// t1.join();
t1_1.join();
// t2.join();
return 0;
}
2.3 Functor¶
// 3.functor (Funciton Object)
class Base {
public:
void operator()(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
// 调用
thread t(Base(), 10);
t.join();
2.4 Função-membro não estática¶
// 4.Non-static member function
class Base {
public:
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
// 调用
thread t(&Base::fun,&b, 10);
t.join();
2.5 Função-membro estática¶
// 4.Non-static member function
class Base {
public:
static void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
// 调用
thread t(&Base::fun, 10);
t.join();
3.join e detach¶
3.1 join¶
- Assim que o thread começa, se quisermos esperar sua conclusão, precisamos chamar join() nesse objeto
- Um join duplo fará o programa terminar
- Antes do join, devemos verificar com joinable() se ele pode sofrer join
void run(int count) {
while (count-- > 0) {
cout << count << endl;
}
std::this_thread::sleep_for(chrono::seconds(3));
}
int main() {
thread t1(run, 10);
cout << "main()" << endl;
t1.join();
if (t1.joinable()) {
t1.join();
}
cout << "main() after" << endl;
return 0;
}
3.2 detach¶
- Isso é usado para separar do thread pai o thread recém-criado
- Antes de separar o thread, verifique sempre se ele é joinable; caso contrário, pode ocorrer uma dupla separação, e um detach() duplo fará o programa terminar
- Se temos um thread separado e a função main está retornando, a execução do thread separado será suspensa
void run(int count) {
while (count-- > 0) {
cout << count << endl;
}
std::this_thread::sleep_for(chrono::seconds(3));
}
int main() {
thread t1(run, 10);
cout << "main()" << endl;
t1.detach();
if(t1.joinable())
t1.detach();
cout << "main() after" << endl;
return 0;
4.Seção crítica e mutex¶
4.1 O que é uma seção crítica (Critical Section)?¶
Uma seção crítica é um trecho de código que, para o programa funcionar corretamente, só pode ser executado por um thread por vez. Se dois threads (ou processos) executarem simultaneamente o código dentro da seção crítica, o programa pode deixar de ter o comportamento correto.
4.2 Apenas incrementar uma variável é uma seção crítica?¶
Pode ser.
O processo de incrementar uma variável (i++) tem três etapas:
- Copiar o conteúdo da memória para um registrador da CPU. load
- Incrementar esse valor na CPU. increment
- Armazenar o novo valor na memória. store
Se essa posição de memória (por exemplo, a variável i abaixo) puder ser acessada por apenas um thread, não haverá disputa nem seção crítica associada a i. Mas a variável sum é uma variável global, que pode ser acessada por dois threads. Os dois threads podem tentar incrementá-la ao mesmo tempo.
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
int sum = 0; //shared
mutex m;
void *countgold() {
int i; //local to each thread
for (i = 0; i < 10000000; i++) {
sum += 1;
}
return NULL;
}
int main() {
thread t1(countgold);
thread t2(countgold);
//Wait for both threads to finish
t1.join();
t2.join();
cout << "sum = " << sum << endl;
return 0;
}
4.3 Como garantir que apenas um thread por vez acesse a variável global?¶
Se um thread está atualmente na seção crítica, queremos que o outro espere até o primeiro terminar. Para isso, podemos usar um mutex (abreviação de mutual exclusion, exclusão mútua).
Metáfora visual do mutex:

Uma maneira simples de impedir a entrada de outros é colocar uma tranca na porta. A primeira pessoa a chegar tranca a porta; quem chega depois vê a porta trancada e espera na fila até a tranca abrir para entrar. Isso se chama "mutex" (Mutual exclusion, abreviado Mutex), e evita que vários threads leiam e escrevam simultaneamente a mesma região de memória.
Com o código acima, a saída já sai correta:sum = 20000000.
Referências¶
https://www.youtube.com/watch?v=eZ8yKZo-PGw&list=PLk6CEY9XxSIAeK-EAh3hB4fgNvYkYmghp&index=4
Tópicos relacionados¶
- Guia de C++ Concurrency in Action | C++那些事
- Depuração de Deadlocks em C++ | C++那些事
- Vetorização SIMD em C++ | C++那些事
- STL Paralela do C++17 | C++那些事
- False Sharing e Linhas de Cache em C++ | C++那些事
- Depuração All-Stop multi-thread com GDB | C++那些事
- Detecção de Erros de Concorrência com Helgrind | C++那些事
- Princípio do event loop do libev | C++那些事
- Implementação interna de std::thread em C++ | C++那些事
- C++ std::thread e jthread | C++那些事