C++多线程初探
介绍并发与并行概念、线程基本原理及 C++ 多线程支持演进,涵盖为何并发、何时使用,以及 std::thread 的简单示例。
C++ 多线程初探
什么是并发?
并发是指两个或多个独立活动在同一时间段内交替或同时发生。生活中常见的例子包括:一边走路一边说话、多人各自完成不同任务等。
计算机中的并发
在计算机系统中,并发指的是在同一个系统上同时运行多个任务。传统的单核处理器通过“任务切换”模拟出并发的效果;而现代多核处理器实现了真正的并行执行。
真并行 vs. 任务切换
- 真并行:多核处理器可同时处理多个任务。
- 任务切换:单核处理器通过频繁切换任务来模拟并行效果,但存在上下文切换的性能开销。
并发的实现方式
多进程并发
每个进程独立运行,拥有独立的地址空间。进程间通信(IPC)成本较高,但更安全可靠,适合大型分布式系统。
多线程并发
在一个进程中运行多个线程,共享同一内存空间。线程之间通信更高效,但需手动保证线程安全,编程难度较大。
并发与并行的区别
- 并发(Concurrency):注重任务的逻辑独立性和响应能力。
- 并行(Parallelism):强调任务的同时执行,提高整体吞吐量与性能。
为什么使用并发?
使用并发有两个主要目的:
分离关注点(Separation of Concerns)
通过将不同功能模块放入不同线程(如 GUI 与后台任务),可以提升代码的可读性和响应性。例如,一个视频播放程序可以使用一个线程处理解码播放,另一个线程响应用户操作。
提升性能
多核处理器时代,只有并发程序才能发挥出硬件的全部潜力。
- 任务并行:不同线程处理算法的不同部分。
- 数据并行:多个线程处理同样操作但作用于不同数据。
- 提升吞吐:在单位时间内处理更多数据。
何时不使用并发?
当并发带来的收益小于其成本(调试难度、资源消耗、维护复杂性)时,不应使用并发。例如:
- 任务太短,线程启动/切换成本高;
- 系统资源(内存、栈空间)有限;
- 存在线程竞争或过度上下文切换。
C++ 与多线程:历史与演进
C++98 时代
没有对线程的标准支持,也没有定义内存模型。开发者只能依赖平台特定的 API,如 POSIX、Windows API 等。
第三方库支持
为简化线程编程,出现了诸如 Boost.Thread、ACE、MFC 等库。这些库在一定程度上提供了抽象,但缺乏统一标准,移植性差。
C++11 的重大变革
C++11 引入了:
- 内存模型;
<thread>
线程库;- 互斥量、条件变量、原子类型等核心工具。
此后,C++14 增加新的同步原语,C++17 更是引入了并行算法库(如 std::for_each(std::execution::par, ...)
),极大提升了编写并发程序的能力与便捷性。
效率与可移植性
- C++ 标准库线程功能设计时尽量保证低抽象代价(即高级 API 性能不逊于底层 API)。
- 对性能敏感场景可使用原子类型或平台原生 API(通过
native_handle()
获取底层句柄)。 - 正确设计优于“死抠优化”。
C++ 并发程序入门示例
一个最简单的并发程序示例如下:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello Concurrent World\n";
}
int main() {
std::thread t(hello); // 创建新线程执行 hello()
t.join(); // 等待新线程执行完成
}
对比传统的 Hello World
:
1
2
3
4
5
#include <iostream>
int main() {
std::cout << "Hello World\n";
}
上面使用 std::thread
启动了一个线程来执行 hello()
函数。虽然此例简单,但它演示了 C++11 如何便捷地创建一个线程。
总结
- 并发指多个任务在同一时间段执行,可能是交替也可能是同时;
- 多线程是实现并发的主要方式,比多进程通信效率高但更容易出错;
- 使用并发需权衡:分离关注点或提升性能 vs. 增加的复杂性与资源开销;
- C++11 起,C++ 标准库对并发支持全面发展;
- 使用
std::thread
创建线程非常简单,但设计良好的并发程序远比启动线程复杂。