Última actualización:2026-10-12
Introducción al multihilo en C++¶
Todo el código de este artículo está disponible en el repositorio de 《C++那些事》.
1. Entender hilos y procesos¶
El hilo es la unidad básica de planificación; el proceso es la unidad básica de asignación de recursos. Un programa puede entenderse como un proceso, y un proceso contiene a su vez varios hilos.
Por ejemplo: el navegador es un proceso, y cada pestaña que abres es un hilo.
Diferencias sencillas entre ambos:
- Espacio de direcciones y otros recursos: los procesos son independientes entre sí, mientras que los hilos de un mismo proceso los comparten. Los hilos de un proceso no son visibles para otros procesos.
- Comunicación: entre procesos se usa la comunicación IPC; entre hilos se puede comunicar leyendo y escribiendo directamente el segmento de datos del proceso (por ejemplo, variables globales), con la ayuda de mecanismos de sincronización y exclusión mutua para garantizar la consistencia de los datos.
- Planificación y conmutación: el cambio de contexto entre hilos es mucho más rápido que el cambio de contexto entre procesos.
- En un SO multihilo, el proceso no es una entidad ejecutable.
Sobre la comunicación IPC y la comunicación entre hilos se publicará más adelante un artículo aparte.
2. Cinco formas de crear hilos¶
- Punteros a función
- Funciones Lambda
- Functor (funtor)
- Funciones miembro no estáticas
- Funciones miembro estáticas
2.1 Punteros a función¶
// 1.函数指针
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
// 调用
std::thread t1(fun, 10);
t1.join();
2.2 Funciones 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 Funtores¶
// 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 Funciones miembro no estáticas¶
// 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 Funciones miembro estáticas¶
// 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 y detach¶
3.1 join¶
- Una vez que el hilo comienza, si queremos esperar a que termine, debemos llamar a join() sobre ese objeto
- Un join doble provoca la terminación del programa
- Antes de hacer join deberíamos comprobar explícitamente si puede hacerse join, usando joinable()
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¶
- Se usa para separar del hilo padre el hilo recién creado
- Antes de separar un hilo, comprueba siempre si es joinable; de lo contrario podrías provocar dos separaciones, y un detach() doble provoca la terminación del programa
- Si tenemos un hilo separado y la función main está retornando, la ejecución del hilo separado quedará suspendida
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. Secciones críticas y mutex¶
4.1 ¿Qué es una sección crítica (Critical Sections)?¶
Una sección crítica es un fragmento de código que, para que el programa funcione correctamente, solo puede ejecutarlo un hilo cada vez. Si dos hilos (o procesos) ejecutan a la vez el código dentro de la sección crítica, es posible que el programa deje de comportarse correctamente.
4.2 ¿Es una sección crítica el simple incremento de una variable?¶
Puede que sí.
El proceso de incrementar una variable (i++) tiene tres pasos:
- Copiar el contenido de la memoria a un registro de la CPU. load
- Incrementar ese valor en la CPU. increment
- Almacenar el nuevo valor en memoria. store
Si solo un hilo puede acceder a esa posición de memoria (por ejemplo, la variable i siguiente), no hay situación de contención ni sección crítica asociada a i. Pero la variable sum es una variable global a la que pueden acceder dos hilos. Los dos hilos podrían intentar incrementar la variable al mismo tiempo.
#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;
}
La salida típica del código anterior es que sum totaliza 20000000. Al existir una condición de carrera, cada ejecución del programa imprime un total distinto. El código no impide que dos hilos lean y escriban el total al mismo tiempo. Por ejemplo, ambos hilos copian el valor actual de sum a la CPU donde corre cada hilo (supongamos 123). Los dos hilos incrementan en uno su propia copia. Los dos hilos escriben de vuelta ese valor (124). Si los hilos hubieran accedido al total en momentos distintos, el contador habría sido 125.
4.3 ¿Cómo garantizar que solo un hilo acceda a la vez a una variable global?¶
Si un hilo está actualmente en la sección crítica, queremos que el otro hilo espere hasta que el primero termine. Para ello podemos usar un mutex (abreviatura de exclusión mutua).
Metáfora del mutex:

Una forma sencilla de impedir que otros entren es poner un candado en la puerta. El primero en llegar cierra con llave; quien llega después ve el candado y hace cola en la puerta, esperando a que se abra para entrar. Esto se llama "mutex" (Mutual exclusion, abreviado Mutex), y evita que varios hilos lean y escriban a la vez una misma zona de memoria.
Con el código anterior la salida ya es correcta: sum = 20000000.
Referencias¶
https://www.youtube.com/watch?v=eZ8yKZo-PGw&list=PLk6CEY9XxSIAeK-EAh3hB4fgNvYkYmghp&index=4
Temas relacionados¶
- Guía de lectura de C++ Concurrency in Action | C++那些事
- Localización de interbloqueos en C++ | C++那些事
- Vectorización SIMD en C++ | C++那些事
- STL paralelo de C++17 | C++那些事
- False Sharing y líneas de caché en C++ | C++那些事
- Depuración multihilo All-Stop con GDB | C++那些事
- Detección de errores de concurrencia con Helgrind | C++那些事
- Principios del bucle de eventos de libev | C++那些事
- Implementación interna de std::thread en C++ | C++那些事
- std::thread y jthread en C++ | C++那些事