跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 多线程编程基础:线程管理与同步机制

讲解 C++ 多线程核心价值,涵盖 std::thread 基础使用、生命周期管理(join/detach)、参数传递规则。深入分析线程状态、竞争条件、临界区、互斥锁及条件变量同步机制,并提供虚假唤醒的解决方案与代码示例。

数字游民发布于 2026/3/24更新于 2026/6/336 浏览

一、多线程核心价值

使用多线程的核心目的是提升程序性能、响应性和资源利用率,主要适用于三类场景:

  1. 任务分解:耗时操作(如后台计算)放入子线程,保证主线程(如 GUI 界面)实时响应。
  2. 数据分解:拆分大规模数据处理任务,充分利用多核 CPU 并行计算能力。
  3. 数据流分解:实现读写分离、解耦合(如一个线程读网络数据,一个线程解析数据)。

二、std::thread 核心使用体

2.1 基础使用:安全退出 + 生命周期管理

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <mutex>

using namespace std;

// 1. 原子退出标志(保证多线程可见性,避免内存可见性问题)
atomic<bool> g_is_exit(false);

// 2. 互斥锁(保护临界区,避免竞争状态)
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) {
        // 临界区:使用 RAII 方式管理锁,精准控制作用域
        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;
     ;
}
sleep_for
seconds
1
// 模拟耗时操作
lock_guard<mutex> lock(g_mtx)
" 优雅退出"
int main()
"主线程启动,线程 ID:"
get_id
// ========== 方案 1:join()(推荐,可控安全)==========
"\n----- 测试 join() 方案 -----"
false
thread th_join(ThreadMain, "join 线程")
sleep_for
seconds
3
true
"主线程等待 join 线程退出..."
join
"join 线程已退出"
// ========== 方案 2:detach()(慎用,低可控)==========
"\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(); // 等待 t1 线程结束
    std::cout << "Back in main()\n";
    // main() 函数会等待 t1 线程完成它的任务后才会继续执行。
    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(); // 让 t2 线程独立运行
    std::cout << "Back in main(), t2 is detached and running in background\n";
    // 主线程不会等待 t2 线程结束
    std::this_thread::sleep_for(chrono::seconds(3)); // 给 t2 一些时间来完成
    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); // 创建 unique_lock 并加锁
// 使用 while 循环确保即使发生虚假唤醒也能正确处理
while (!ready) {
    cv.wait(lock); // 如果条件未满足,则解锁 mtx 并等待通知
}
// 当前线程被唤醒后重新检查条件
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);
// lck:一个已加锁的 std::unique_lock<std::mutex> 对象。
// 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 * 100ms, [] { return i == 1; })) {
        std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
    } else {
        std::cerr << "Thread " << idx << " timed out." << '\n';
    }
}

目录

  1. 一、多线程核心价值
  2. 二、std::thread 核心使用体
  3. 2.1 基础使用:安全退出 + 生命周期管理
  4. 2.2 生命周期管理:join() vs detach()
  5. 2.2.1 核心规则
  6. 2.2.2 join() 详解
  7. 2.2.3 detach() 详解
  8. 2.2.4 join() vs detach() 对比表
  9. 2.3 参数传递规则
  10. 核心原则
  11. 三、线程状态与同步核心概念
  12. 3.1 线程生命周期状态
  13. 3.2 多线程竞争与同步核心概念
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • MetaLlama 大模型从入门到部署实战指南
  • 基于FPGA的五级CIC滤波器Verilog设计
  • .NET 集成 GoView 低代码可视化大屏完整方案
  • AI 大模型开发者必读书籍:《GPT 图解大模型是怎样构建的》
  • 二叉树中序遍历的递归与迭代实现
  • MacOS 下使用 Docker 部署 OpenClaw 并对接飞书机器人
  • Flutter 三方库 bavard 鸿蒙适配:语义化聊天协议与分布式通讯封装
  • 数据结构:二叉树核心概念与特性
  • IntelliJ IDEA 与 VS Code Git 标准操作规范
  • 使用 DevCloud 流水线自动化部署 Web 应用
  • Xget:一键加速 Docker 拉取与 GitHub 代码下载
  • Java 多线程并发编程:并发容器与线程协作实战
  • Linux 权限详解:指令运行原理与文件类型解析
  • 基于 Spring Boot 的航空票务管理系统设计与实现
  • ComfyUI 提示词助手实战:通过自动化流程提升 AI 绘画效率
  • KWDB 运维实战:用 SQL 打通 Metrics 与 CMDB 数据融合
  • C++ ODB ORM 核心概念与实战指南
  • 微信小程序原生前端开发入门:从零构建第一个可交互页面
  • AI 可能引发大规模生物风险,专家呼吁建立跨学科团队
  • Stable Diffusion 与 Z-Image-Turbo 模型部署及性能对比实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,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