文章

C++多线程初探

介绍并发与并行概念、线程基本原理及 C++ 多线程支持演进,涵盖为何并发、何时使用,以及 std::thread 的简单示例。

C++多线程初探

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 创建线程非常简单,但设计良好的并发程序远比启动线程复杂。
本文由作者按照 CC BY 4.0 进行授权