第 18.19 節
并发编程
0瀏覽次數0訪問次數--跳出率--平均停留
本节解决什么问题
现代计算机通常有多个 CPU 核心。一个程序如果只在一个线程里顺序执行,就无法让多个耗时任务同时推进。典型场景包括:
- 一个线程保持界面响应,另一个线程处理后台任务。
- 同时等待多个网络请求、定时器或外设事件。
- 把可拆分的计算任务分给多个线程。
并发编程的难点不在于"开线程"本身,而在于生命周期和共享数据。只要两个线程同时读写同一份数据,就必须认真处理同步问题。
学习路线
本节拆成几个文件级子章节来学:
std::thread:创建线程,并用join()等待结束。std::mutex和std::lock_guard:保护共享数据。std::atomic:简单计数器的无锁线程安全操作。std::condition_variable:一个线程等待另一个线程发通知。
如果只启动一个任务,同步和并发的结果可能看起来一样。差异通常要在"两个或更多任务同时等待或同时计算"时才明显。这和 Boost.Asio 中一个定时器看不出异步优势、两个定时器才看出区别是同一个道理。
C++ 标准版本
C++11 引入了标准线程库。C++14、C++17、C++20 又增加了 std::shared_mutex、std::jthread、std::latch、std::barrier 等工具。
本节主要使用 C++11 就有的基础工具。编译时通常需要加线程选项,例如:
g++ demo.cpp -std=c++17 -pthread
常用工具
| 工具 | 作用 | 常见头文件 |
|---|---|---|
std::thread | 创建和管理线程 | <thread> |
std::this_thread::sleep_for | 当前线程休眠一段时间 | <thread>、<chrono> |
std::mutex | 互斥锁,保护共享数据 | <mutex> |
std::lock_guard | RAII 加锁,作用域结束自动解锁 | <mutex> |
std::unique_lock | 更灵活的 RAII 锁,常配合条件变量 | <mutex> |
std::atomic | 原子变量,适合简单计数 | <atomic> |
std::condition_variable | 等待和通知 | <condition_variable> |
并发和并行
| 概念 | 含义 | 例子 |
|---|---|---|
| 并发 | 多个任务在同一段时间内推进 | 一个程序同时等待网络、定时器和用户输入 |
| 并行 | 多个任务真的同时运行 | 多核 CPU 上多个线程同时计算 |
初学时先抓住工程直觉:多个任务都要等待外设、网络、定时器或耗时计算时,让它们同时推进,程序整体会更快或响应更好。
使用建议
- 先保证正确,再考虑性能。
- 能不共享数据就不共享数据,每个线程处理自己的数据最简单。
- 共享数据优先用
mutex保护,简单计数才考虑atomic。 - 锁的作用域尽量小,避免长时间持锁。
- 初学阶段少用
detach(),优先用join()明确等待线程结束。 - C++20 项目可以了解
std::jthread,它析构时会自动请求停止并 join,比std::thread更安全。
工程拓展
在 ROS2 中,多线程回调执行器、回调组和共享状态保护都会涉及线程与锁。在 Boost.Asio 中,多个异步任务可能运行在同一个 io_context 或线程池里,也要考虑回调之间的共享数据。学好 C++ 标准库并发工具,再看这些工程库会轻松很多。