C++多线程编程实现方式汇总

作者:技术拾光时间:2026-07-27 07:56:13浏览:594

C++多线程编程方式包括:POSIX线程适用于Linux/Unix系统,std::thread是C++11标准推荐,std::async用于简单异步任务,OpenMP常用于科学计算循环并行,IntelTBB专注于任务并行,BoostThread用于老项目兼容,QtQThread专用于GUI程序,而MPI是进程并行并非线程库。这些方法各有适用场景。

在 C++ 中,多线程编程的实现方式丰富多样,从底层接口到高层封装,大致可分为多个类别。以下表格展示了整体概览,随后逐一详细解读。

C++多线程编程实现方式汇总

方式标准是否推荐适用场景
POSIX Threads (pthread)POSIXLinux/Unix 系统开发
std::threadC++11现代 C++ 首选
std::asyncC++11简单异步任务
OpenMP编译器扩展科学计算、循环并行
Intel TBB / oneTBB第三方库任务并行
Boost.ThreadBoost老项目兼容
Qt QThreadQtQt GUI 程序
MPI第三方库多进程,不属于线程库

注意最后一项 MPI 不是线程库,而是进程并行库。这个区分很重要,后面会细说。

1. std::thread(现代 C++ 首选)

C++11 标准开始,C++ 终于有了自己的线程库——std::thread。用法非常直接:

#include 
#include 

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 

void* func(void*)
{
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, func, nullptr);
    pthread_join(tid, nullptr);
}

优点:Linux 下功能最全,很多老项目都在用。缺点也很明显:C 风格 API,用起来比较繁琐。

有意思的是,在 Linux 上,很多 std::thread 的实现底层实际上就是调用的 pthread。

3. std::async

适合执行一个简单的异步任务,不需要手动创建线程:

auto result = std::async(std::launch::async,
                         []()
                         {
                             return 100;
                         });

std::cout << result.get();

一句话:省心,但有局限。

4. OpenMP(科学计算最常见)

在科学计算领域,OpenMP 几乎是标配。比如这样:

#pragma omp parallel for
for(int i=0;i

编译时加个选项:

g++ -fopenmp

OpenMP 最大的特点:几乎不用手动管理线程,编译器自动帮你并行化。VASP、LAMMPS、OpenFOAM 这些重量级科学计算软件都大量使用。

更灵活的用法:

#pragma omp parallel
{
    // 多线程
}

5. Intel TBB(oneTBB)

Intel 的任务调度库,比 OpenMP 更灵活,适合复杂任务图:

tbb::parallel_for(
    0, N,
    [&](int i)
    {
        ...
    });

很多工业软件都在用,比如 OpenCV、oneAPI。

6. Boost.Thread

现代 C++ 出现以前,Boost.Thread 是最流行的选择。现在基本被 std::thread 替代,但老项目里可能还有。

boost::thread t(func);

7. Qt 的 QThread

Qt GUI 开发必备,主要解决界面不阻塞的问题,还集成了信号槽机制。

class Worker : public QThread
{
    void run() override
    {
        ...
    }
};

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 

std::thread t([] {
    // 工作
});
t.join();

C++20 中更推荐考虑 std::jthread:

#include 

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<: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++
pthreadPOSIX 底层线程C、Linux 系统开发、底层控制
OpenMP标准化共享内存并行 API科学计算、规则循环
oneTBB任务并行运行时复杂任务图、线程池
MPI分布式进程通信标准集群、超算、多节点计算

另外,OpenMP 不是简单的“编译器私有扩展”,它有独立的公开规范,只是通过编译器指令和运行时库实现。

一句话总结:

普通 C++ 并发优先考虑标准线程或成熟任务框架;规则科学计算优先考虑 OpenMP;跨节点并行使用 MPI;pthread 更接近 Linux 底层实现和控制接口。