在 c++ 中,实现多线程编程的方法有很多,从底层到高层大致可以分为以下几类。
| 方式 | 标准 | 是否推荐 | 适用场景 |
|---|---|---|---|
posix threads (pthread) | posix | ⭐⭐⭐⭐ | linux/unix 系统开发 |
std::thread | c++11 | ⭐⭐⭐⭐⭐ | 现代 c++ 首选 |
std::async | c++11 | ⭐⭐⭐ | 简单异步任务 |
| openmp | 编译器扩展 | ⭐⭐⭐⭐⭐ | 科学计算、循环并行 |
| intel tbb / onetbb | 第三方库 | ⭐⭐⭐⭐ | 任务并行 |
| boost.thread | boost | ⭐⭐⭐ | 老项目兼容 |
| qt qthread | qt | ⭐⭐⭐⭐ | qt gui 程序 |
| mpi | 第三方库 | ⭐ | 多进程,不属于线程库 |
注意最后一项 mpi 不是线程库,而是进程并行库。
1. std::thread(现代 c++ 首选)
c++11 开始标准提供。
#include <thread>
#include <iostream>
void work(int id)
{
std::cout << id << std::endl;
}
int main()
{
std::thread t1(work, 1);
std::thread t2(work, 2);
t1.join();
t2.join();
}
优点:
- 跨平台
- stl 风格
- 推荐新项目使用
配套还有:
std::mutex std::lock_guard std::unique_lock std::condition_variable std::atomic std::future
2. pthread(linux 最经典)
这是 linux 系统接口。
#include <pthread.h>
void* func(void*)
{
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, func, nullptr);
pthread_join(tid, nullptr);
}
优点:
- linux 下功能最完整
- 很多老项目都在用
缺点:
- c 风格 api
- 使用起来比较繁琐
很多 std::thread 的实现底层实际上会调用 pthread(在 linux 上)。
3. std::async
适合执行一个异步任务。
auto result = std::async(std::launch::async,
[]()
{
return 100;
});
std::cout << result.get();
不需要自己创建线程。
4. openmp(科学计算最常见)
例如:
#pragma omp parallel for
for(int i=0;i<n;i++)
{
a[i]+=b[i];
}
编译:
g++ -fopenmp
openmp 特点:
- 几乎不用管理线程
- 编译器自动完成
vasp、lammps、openfoam 都大量使用。
例如:
#pragma omp parallel
{
// 多线程
}
5. intel tbb(onetbb)
intel 的任务调度库。
例如:
tbb::parallel_for(
0,
n,
[&](int i)
{
...
});
比 openmp 更灵活。
很多工业软件使用:
- opencv
- oneapi
6. boost.thread
现代 c++ 出现以前最流行。
boost::thread t(func);
现在基本被:
std::thread
替代。
7. qt 的 qthread
qt gui 开发必备。
例如:
class worker : public qthread
{
void run() override
{
...
}
};
主要解决:
- gui 不阻塞
- 信号槽
8. 线程池(thread pool)
严格来说不是一种线程 api,而是一种编程模式。
例如:
threadpool pool(8); pool.enqueue(task1); pool.enqueue(task2);
优点:
- 避免频繁创建线程
- 性能最好
很多服务器都采用这种方式。
c++23 标准没有正式线程池,很多项目使用:
- bs::thread_pool
- onetbb
- folly
- boost.asio
9. mpi(容易和线程混淆)
mpi:
mpi_init(...); mpi_comm_rank(...); mpi_send(...);
它创建的是:
process0 process1 process2
不是:
thread1 thread2
所以:
mpi 是多进程,不是多线程。
在 hpc 中通常是混合并行
像 vasp、gromacs 等 hpc 软件通常会组合使用多种技术:
多个节点
│
▼
mpi(多进程)
│
▼
openmp(每个进程多个线程)
│
▼
cuda/hip(gpu 并行)
例如:
node1
├── mpi rank0
│ ├── thread0
│ ├── thread1
│ └── thread2
└── mpi rank1
├── thread0
├── thread1
└── gpu0
因此,一个 vasp 作业中通常会同时用到 mpi + openmp + cuda/hip,分别负责不同层次的并行。
实际工作中的常见选择
如果按行业来看,大致可以这样归纳:
- 普通 c++ 应用开发:
std::thread是首选,linux 老项目中pthread仍然很多。 - 科学计算 / hpc:openmp + mpi 是主流,gpu 场景再结合 cuda/hip。
- gui 开发(qt):通常使用
qthread。 - 高性能服务器:更多使用线程池、任务调度库(如 onetbb、boost.asio、folly 等),而不是频繁创建和销毁线程。
这张表有一定误导性。mpi 和 openmp 并不是“见得少”,而是它们主要集中在科学计算和 hpc 领域;在通用 c++ 应用、服务端和 gui 开发中相对少见。
另外,现代 c++ 新项目通常优先使用 std::thread,并不是普遍优先使用 pthread。pthread 更多见于 linux 系统软件、底层运行时和历史项目。
先区分三种并行模型
| 技术 | 并行单位 | 内存模型 | 典型范围 |
|---|---|---|---|
pthread / std::thread | 线程 | 共享内存 | 单个进程、单台机器 |
| openmp | 线程 | 共享内存 | 单进程、单个计算节点 |
| mpi | 进程 | 分布式内存、消息传递 | 单节点或多个计算节点 |
mpi 不是线程库。它主要解决:
节点 a 的进程如何和节点 b 的进程交换数据
openmp 和线程库主要解决:
同一进程内多个 cpu 核心如何共享数据并行计算
为什么通用 c++ 项目中 mpi 不常见
mpi 主要面向多进程、分布式内存计算。例如超算中:
计算节点 0:mpi rank 0~63 计算节点 1:mpi rank 64~127 计算节点 2:mpi rank 128~191
进程之间通过消息通信:
mpi_send(...); mpi_recv(...); mpi_allreduce(...);
它特别适合:
- vasp、lammps、gromacs 等科学计算;
- 流体、气象、量子化学计算;
- 大规模矩阵和网格计算;
- 跨多个服务器或超算节点运行。
普通桌面程序、web 服务或 gui 程序通常不需要同时使用几十台服务器做一次紧密耦合计算,因此很少使用 mpi。
另外,mpi 程序通常需要专门启动:
mpirun -np 64 ./program
它的进程部署、通信、负载均衡和故障处理也比普通线程复杂。
所以不是 mpi 不成熟,而是它解决的问题与普通多线程不同。在 hpc 中,mpi 非常常见,甚至可以说是基础设施。
为什么通用 c++ 中 openmp 看起来也不多
openmp 最擅长的是规则循环并行:
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
c[i] = a[i] + b[i];
}
这类代码在科学计算中很多,因此 openmp 非常合适。
但通用 c++ 程序的并发任务通常不是规则循环,例如:
- 网络连接处理;
- 后台任务队列;
- 文件异步读写;
- gui 后台计算;
- 长生命周期工作线程;
- 生产者/消费者;
- 线程池;
- 复杂任务依赖。
这些场景通常需要显式控制:
创建线程 等待线程 条件变量 互斥锁 任务队列 线程生命周期 异常和退出
std::thread、std::jthread、线程池或异步框架更适合这些需求。
openmp 的优势是快速表达“把这一段计算并行化”,而不是构建完整的并发软件架构。
为什么有些 c++ 项目使用 pthread
pthread 是 posix 线程接口,长期以来是 linux/unix 的底层线程标准:
pthread_create(...); pthread_join(...); pthread_mutex_lock(...);
它常见的原因主要有以下几个。
1. 历史原因
c++11 以前,标准 c++ 没有正式的线程库。linux c/c++ 项目普遍使用 pthread。许多成熟项目延续了原有设计。
2. 同时支持 c 和 c++
pthread 是 c 接口,可以被 c 和 c++ 使用。操作系统、数据库和底层库经常包含大量 c 代码。
3. linux 平台的底层控制
pthread 提供一些平台相关能力,例如:
pthread_setaffinity_np pthread_setschedparam pthread_setname_np
可用于:
- cpu 亲和性;
- 实时调度策略;
- 线程栈大小;
- 线程名称;
- numa 和性能调优。
虽然 c++ 标准线程可以通过 native_handle() 获取底层 pthread,但底层系统软件有时会直接使用 pthread。
4. 很多高级线程库最终建立在 pthread 上
在 linux 中:
std::thread
openmp runtime
onetbb
各种线程池
↓
通常最终使用 pthread/clone 创建系统线程
因此 pthread 是底层机制,而 openmp、onetbb 和 std::thread 是更高级的编程接口。
新 c++ 项目该用什么
如果是普通现代 c++ 项目,通常优先:
#include <thread>
std::thread t([] {
// 工作
});
t.join();
c++20 中更推荐考虑 std::jthread:
#include <thread>
std::jthread t([] {
// 工作
});
std::jthread 析构时自动等待线程,还支持协作式停止,比直接管理 std::thread 更安全。
只有在以下场景才更可能直接使用 pthread:
- 项目主体是 c;
- 必须兼容较老的 c++ 标准;
- 强依赖 posix 接口;
- 需要精细控制线程调度、栈和亲和性;
- 正在维护已有 pthread 项目。
openmp 和普通线程的核心差异
假设要并行处理数组。
使用 openmp:
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
c[i] = a[i] + b[i];
}
程序员描述的是:
这个循环可以并行。
使用线程库时:
std::vector<std::thread> threads;
for (int t = 0; t < num_threads; ++t) {
threads.emplace_back([&, t] {
int begin = t * n / num_threads;
int end = (t + 1) * n / num_threads;
for (int i = begin; i < end; ++i) {
c[i] = a[i] + b[i];
}
});
}
for (auto& thread : threads) {
thread.join();
}
程序员需要自己处理:
- 创建多少线程;
- 每个线程处理哪一段;
- 如何捕获变量;
- 如何等待线程结束。
因此对于规则数值循环,openmp 更简洁;对于复杂并发架构,显式线程或任务系统更灵活。
mpi、openmp 经常一起使用
在超算中,典型模式不是二选一,而是混合并行:
多个计算节点
↓ mpi
每个节点启动若干 mpi 进程
↓ openmp
每个 mpi 进程再启动若干 cpu 线程
例如两台节点,每台 64 核:
每节点 4 个 mpi 进程 每个 mpi 进程 16 个 openmp 线程 总计:2 × 4 × 16 = 128 个 cpu 核
常见启动方式类似:
export omp_num_threads=16 mpirun -np 8 ./program
vasp 就经常采用 mpi、openmp 和 gpu 加速的组合。
对原表的修正
更合理的评价是:
| 技术 | 定位 | 推荐场景 |
|---|---|---|
std::thread / std::jthread | 标准 c++ 线程 | 现代通用 c++ |
| pthread | posix 底层线程 | c、linux 系统开发、底层控制 |
| openmp | 标准化共享内存并行 api | 科学计算、规则循环 |
| onetbb | 任务并行运行时 | 复杂任务图、线程池 |
| mpi | 分布式进程通信标准 | 集群、超算、多节点计算 |
另外,openmp 不是简单的“编译器私有扩展”,它有独立的公开规范,只是通过编译器指令和运行时库实现。
一句话总结:
普通 c++ 并发优先考虑标准线程或成熟任务框架;规则科学计算优先考虑 openmp;跨节点并行使用 mpi;pthread 更接近 linux 底层实现和控制接口。
以上就是c++实现多线程编程的方式汇总的详细内容,更多关于c++多线程编程方式的资料请关注代码网其它相关文章!
发表评论