跳到主要内容 C++ 多线程编程基础:线程管理与同步机制 | 极客日志
C++ 算法
C++ 多线程编程基础:线程管理与同步机制 讲解 C++ 多线程核心价值,涵盖 std::thread 基础使用、生命周期管理(join/detach)、参数传递规则。深入分析线程状态、竞争条件、临界区、互斥锁及条件变量同步机制,并提供虚假唤醒的解决方案与代码示例。
数字游民 发布于 2026/3/24 更新于 2026/4/16 3 浏览一、多线程核心价值
使用多线程的核心目的是提升程序性能、响应性和资源利用率,主要适用于三类场景:
任务分解 :耗时操作(如后台计算)放入子线程,保证主线程(如 GUI 界面)实时响应。
数据分解 :拆分大规模数据处理任务,充分利用多核 CPU 并行计算能力。
数据流分解 :实现读写分离、解耦合(如一个线程读网络数据,一个线程解析数据)。
二、std::thread 核心使用体
2.1 基础使用:安全退出 + 生命周期管理
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <mutex>
using namespace std;
atomic<bool > g_is_exit (false ) ;
mutex g_mtx;
void ThreadMain (const string& thread_name) {
cout << thread_name << " 启动,线程 ID:" << this_thread::get_id () << endl;
int count = 0 ;
while (!g_is_exit) {
lock_guard<mutex> lock (g_mtx) ;
cout << thread_name << " 运行中(计数:" << ++count << ")" << endl;
this_thread:: (chrono:: ( ));
}
;
cout << thread_name << << endl;
}
{
cout << << this_thread:: () << endl;
cout << << endl;
g_is_exit = ;
;
this_thread:: (chrono:: ( ));
g_is_exit = ;
cout << << endl;
th_join. ();
cout << << endl;
cout << << endl;
g_is_exit = ;
;
th_detach. ();
this_thread:: (chrono:: ( ));
g_is_exit = ;
this_thread:: (chrono:: ( ));
cout << << endl;
;
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
sleep_for
seconds
1
lock_guard<mutex> lock (g_mtx)
" 优雅退出"
int main ()
"主线程启动,线程 ID:"
get_id
"\n----- 测试 join() 方案 -----"
false
thread th_join (ThreadMain, "join 线程" )
sleep_for
seconds
3
true
"主线程等待 join 线程退出..."
join
"join 线程已退出"
"\n----- 测试 detach() 方案 -----"
false
thread th_detach (ThreadMain, "detach 线程" )
detach
sleep_for
seconds
3
true
sleep_for
seconds
1
"\n主线程执行完毕"
return
0
2.2 生命周期管理:join() vs detach()
2.2.1 核心规则 std::thread 不会自动等待子线程完成,thread 对象销毁时若线程仍处于 joinable 状态(未调用 join/detach),会触发 std::terminate() 终止整个程序 。必须通过 join() 或 detach() 主动管理,让 thread 对象变为 non-joinable 状态;核心结论:thread 对象销毁时线程仍运行 → 程序必崩溃。
2.2.2 join() 详解
描述 :join() 方法让调用它的线程(通常是主线程)等待,直到被调用 join() 的线程完成其任务。这对于确保某些操作在特定线程完成后才进行非常有用。
使用场景 :当你需要确保某个线程的任务在继续执行之前已经完成时使用。
注意事项 :调用 join() 的线程必须处于可加入状态,即它不能已经被加入或分离。每个线程只能被加入一次。
#include <iostream>
#include <thread>
#include <chrono>
void thread_function (int id) {
std::cout << "Thread " << id << " is running\n" ;
std::this_thread::sleep_for (std::chrono::seconds (2 ));
std::cout << "Thread " << id << " has finished\n" ;
}
int main () {
std::thread t1 (thread_function, 1 ) ;
t1. join ();
std::cout << "Back in main()\n" ;
return 0 ;
}
在这个例子中,主线程会等待 t1 线程完成它的任务后才会继续执行,因此输出顺序将是:Thread 1 is running, 等待两秒,Thread 1 has finished, 最后是 Back in main()。
2.2.3 detach() 详解
描述 :detach() 方法使得一个线程独立运行,不再与创建它的线程关联。这意味着主线程不会等待被分离的线程完成。
使用场景 :当你希望一个线程在后台独立运行,并且不需要等待它完成时使用。
注意事项 :一旦调用了 detach(),你将失去对线程的直接控制,无法通过 join() 来等待它的完成。如果程序结束而线程仍在运行,则该线程可能会被强制终止,因此需谨慎处理资源释放等问题。
#include <iostream>
#include <thread>
#include <chrono>
void detached_thread_function (int id) {
std::cout << "Detached Thread " << id << " is running\n" ;
std::this_thread::sleep_for (std::chrono::seconds (2 ));
std::cout << "Detached Thread " << id << " has finished\n" ;
}
int main () {
std::thread t2 (detached_thread_function, 2 ) ;
t2. detach ();
std::cout << "Back in main(), t2 is detached and running in background\n" ;
std::this_thread::sleep_for (chrono::seconds (3 ));
std::cout << "Main thread ends\n" ;
return 0 ;
}
在这个例子中,t2 线程被分离并在后台独立运行。主线程不会等待 t2 线程结束,而是立即继续执行。为了确保 t2 线程有足够的时间完成其任务,在主线程结束前让它睡眠了三秒钟。输出顺序可能是:Detached Thread 2 is running, Back in main(), t2 is detached and running in background, 再等待一段时间后显示 Detached Thread 2 has finished, 最后是 Main thread ends。注意,由于 t2 是分离的,如果主线程提前结束,可能看不到 Detached Thread 2 has finished 的输出。
2.2.4 join() vs detach() 对比表 特性 join() detach() 执行逻辑 阻塞调用线程,直到子线程执行完毕 立即返回,不阻塞调用线程 线程归属 子线程仍由 thread 对象管理 子线程转为后台守护线程,与对象分离 资源依赖 子线程可安全访问调用线程资源(直到 join) 访问调用线程栈资源易触发野指针/崩溃 可控性 高(可等待、获取结果) 低(无法控制、无法获取结果) 适用场景 需要等待子线程完成、关心执行结果 无需控制子线程、不关心执行结果 退出风险 无(子线程完成后主线程才继续) 主线程提前退出会强制终止子线程
2.3 参数传递规则 参数类型 示例代码 核心注意事项 普通函数 + 值/指针 thread th(func, &obj);栈对象指针需保证生命周期长于子线程,否则野指针 类成员函数 thread th(&Class::func, &obj);必须传递对象地址作为 this 指针 引用传递 thread th(func, ref(obj));必须用 std::ref() 显式标识,否则按值拷贝
核心原则
参数生存期必须长于访问它的所有线程(detach() 场景需重点注意);detach() 本身不导致崩溃,核心问题是子线程访问已释放的栈资源;优先用 join()+ 原子标志位 实现优雅退出,减少 detach() 使用。
三、线程状态与同步核心概念
3.1 线程生命周期状态 状态 说明 初始化(Init) 线程正在被创建,尚未进入就绪队列 就绪(Ready) 线程已创建完成,在就绪列表中等待 CPU 调度 运行(Running) 线程获得 CPU 时间片,正在执行逻辑 阻塞(Blocked) 线程被挂起,暂时放弃 CPU 使用权(如等待锁、延时、等待事件/信号量等) 退出(Exit) 线程运行结束,等待父线程回收控制块资源(仅释放栈资源,堆资源需手动释放)
3.2 多线程竞争与同步核心概念 概念 定义 竞争状态(Race Condition) 多线程同时读写共享数据 ,导致结果不可预测(程序 Bug 的核心来源) 临界区(Critical Section) 读写共享数据的代码片段(必须保证单线程访问,否则触发竞争状态) 互斥锁(mutex) 同步原语,保证临界区代码互斥执行 (同一时间只有一个线程能进入临界区)
虚假唤醒 :当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件未得到满足时,就会发生虚假唤醒。发生虚假唤醒通常是因为在发出条件变量信号和等待线程最终运行之间,另一个线程运行并更改了条件,导致 wait 的线程被唤醒后,实际条件却未满足。比如我们在 notify_all() 时多个线程都被唤醒,但此时实际共享区却只有少数几个线程可以操作,这时就会造成其他线程被虚假唤醒,可以在 wait 唤醒后再次进行检测 condition 解决虚假唤醒。
解决办法 :在条件变量阻塞的代码处增加一个 while 循环,如果被唤醒就要检查一下条件是否符合,如果不符合则要再次进入阻塞等待。这样既避免了忙等待,又避免了虚假唤醒问题。
std::unique_lock<std::mutex> lock (mtx) ;
while (!ready) {
cv.wait (lock);
}
if (ready) {
std::cout << "Worker thread is processing..." << std::endl;
} else {
std::cout << "Unexpected wake-up!" << std::endl;
}
条件变量 :在 C 语言中使用 pthread_cond_wait 函数作为条件变量。在 C++11 以后,可以使用 condition_variable 实现多个线程间的同步,头文件是 #include <condition_variable>。
条件变量主要包括两个动作 :一个线程因等待条件变量的条件成立而挂起;另外一个线程使条件成立从而给出唤醒线程的信号,从而唤醒被等待的线程;
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是 std::mutex,并且管理这个锁只能是 std::unique_lock<std::mutex> 等 RAII 模板类。分别是使用以下两个方法实现:等待条件成立使用的是 condition_variable 类成员 wait、wait_for 或 wait_until。唤醒信号使用的是 condition_variable 类成员 notify_one 或者 notify_all 函数。
condition_variable 支持的函数如下:
构造函数 : 它只有默认构造函数,拷贝构造函数和赋值符号重载均被禁止 condition_variable(const condition_variable&) = delete;,operator= [delete];
wait : wait 目前支持 wait,wait_for,wait_until 等三种操作,分别对应不同的场景:
wait :
初始检查:首先调用谓词 pred 检查条件是否满足。如果条件满足,则直接返回,不阻塞线程。
解锁并等待:如果条件不满足,则解锁互斥量并使当前线程进入等待状态。
唤醒并重新检查:当线程被唤醒时,它会重新获取互斥量的锁,并再次调用谓词 pred 检查条件是否满足。如果条件仍然不满足,则继续等待;如果条件满足,则退出 wait 并继续执行后续代码。
template <class Predicate>
void wait (std::unique_lock<std::mutex>& lck, Predicate pred) ;
wait_for :初始检查调用谓词:调用提供的谓词函数 pred 来检查条件是否已经满足。直接返回:如果谓词返回 true,表示条件已经满足,wait_for 直接返回 true,不进行任何等待操作。解锁并等待解锁互斥量:如果谓词返回 false,表示条件尚未满足,wait_for 会自动解锁互斥量 lck 并使当前线程进入等待状态。设置定时器:开始计时,等待指定的相对时间段 rel_time(如 100ms)。阻塞线程:线程在此期间阻塞,直到以下情况之一发生:
收到通知:其他线程通过 notify_one() 或 notify_all() 发出通知。
超时:指定的等待时间到达。唤醒并重新检查重新获取锁:当线程被唤醒后,它会尝试重新获取互斥量 lck 的锁。再次调用谓词:一旦重新获取了锁,wait_for 会再次调用谓词 pred 检查条件是否满足。条件满足:如果谓词返回 true,则 wait_for 返回 true,线程继续执行后续代码。条件不满足:如果是因为超时导致的唤醒,则 wait_for 返回 false。如果是因为通知导致的唤醒但条件仍不满足,则线程将继续等待(根据具体实现和上下文,可能需要再次调用 wait_for 或者处理其他逻辑)
template <class Rep, class Period, class Predicate>
bool wait_for (
std::unique_lock<std::mutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time,
Predicate stop_waiting) ;
wait_for 和 wait_until 对比
wait_for:接受一个相对时间段,它定义了线程愿意等待的最大时长。
wait_until:接受一个绝对时间点,它定义了线程应该停止等待的具体时刻。
template <class Predicate>
bool wait_until (std::unique_lock<std::mutex>& lock,
const std::chrono::time_point<Clock, Duration>& timeout_time,
Predicate pred) ;
#include <iostream>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
std::condition_variable cv;
std::mutex cv_m;
int i;
void waits (int idx) {
std::unique_lock<std::mutex> lk (cv_m) ;
if (cv.wait_for (lk, idx * 100 ms, [] { return i == 1 ; })) {
std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n' ;
} else {
std::cerr << "Thread " << idx << " timed out." << '\n' ;
}
}