C++创建线程的几种方法详解

 更新时间:2026年10月14日 09:13:58   作者:Lzg_na  
从 C++11 开始,标准库正式引入了线程支持,让开发者无需依赖平台特定的 API即可编写跨平台的多线程代码,在众多线程创建方式中,std::thread、std::async 和 std::jthread 是最常用的三种,本文将逐一介绍它们的用法、差异以及选择时机,需要的朋友可以参考下

1. 引言

从 C++11 开始,标准库正式引入了线程支持,让开发者无需依赖平台特定的 API(如 Windows 的 CreateThread 或 POSIX 的 pthread_create)即可编写跨平台的多线程代码。

在众多线程创建方式中,std::thread、std::async 和 std::jthread 是最常用的三种。它们各有特点,适用于不同的场景。本文将逐一介绍它们的用法、差异以及选择时机,帮助你在实际项目中做出合适的选择。

2. std::thread:最基础的线程类

2.1 基本用法

构造函数原型

// C++11 起
template<class Function, class... Args>
explicit thread(Function&& f, Args&&... args);

// 移动构造(C++11 起)
thread(thread&& other) noexcept;
  • f:要在线程中执行的可调用对象(函数指针、函数对象、Lambda 等)。
  • args...:传递给 f 的参数,按值传递到新线程。
  • 移动构造允许将线程所有权转移给另一个 thread 对象。

std::thread 是 C++11 引入的线程类,它直接创建一个操作系统线程,并在该线程上执行指定的可调用对象(函数、函数对象、Lambda 表达式等)。

#include <iostream>
#include <thread>

void hello() {
    std::cout << "Hello from thread, id = " << std::this_thread::get_id() << std::endl;
}

int main() {
    std::thread t(hello);   // 创建线程并启动
    t.join();               // 等待线程结束
    return 0;
}

也可以传递参数:

#include <iostream>
#include <thread>

void print_sum(int a, int b) {
    std::cout << "Sum = " << (a + b) << std::endl;
}

int main() {
    std::thread t(print_sum, 3, 5);
    t.join();
    return 0;
}

2.2 关键注意事项

  • 必须调用 join() 或 detach():std::thread 对象析构时,如果线程仍处于 joinable 状态,程序会调用 std::terminate 直接终止。因此,务必在合适的时机调用 join()(等待线程结束)或 detach()(分离线程,让其后台运行)。
  • 不可复制,只能移动:std::thread 是 move-only 类型,不能拷贝,但可以通过 std::move 转移所有权。
  • 异常安全:如果线程函数抛出异常且未被捕获,程序会终止。建议在线程函数内部捕获所有异常。
#include <iostream>
#include <thread>

int main() {
    std::thread t([] {
        try {
            // 可能抛出异常的代码
            throw std::runtime_error("error");
        } catch (const std::exception& e) {
            std::cerr << "Caught: " << e.what() << std::endl;
        }
    });
    t.join();
    return 0;
}

2.3 适用场景

  • 需要完全控制线程生命周期(何时等待、何时分离)。
  • 执行耗时任务,且不需要直接获取返回值。
  • 需要精细管理线程数量、优先级等底层细节。

3. std::async:异步任务与返回值

3.1 基本用法

函数原型

// C++11 起
template<class F, class... Args>
future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
async(F&& f, Args&&... args);

// C++11 起
template<class F, class... Args>
future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
async(launch policy, F&& f, Args&&... args);
  • policy:启动策略,取 std::launch::async 或 std::launch::deferred(可组合)。
  • f:要异步执行的可调用对象。
  • args...:传递给 f 的参数。
  • 返回 std::future,用于获取结果或异常。

std::async 是 C++11 引入的异步任务工具。它接受一个可调用对象,返回一个 std::future 对象,通过 future.get() 可以获取任务的返回值或异常。

#include <iostream>
#include <future>

int compute(int x) {
    return x * x;
}

int main() {
    std::future<int> fut = std::async(std::launch::async, compute, 7);
    int result = fut.get();   // 阻塞直到任务完成,获取返回值
    std::cout << "Result = " << result << std::endl;
    return 0;
}

3.2 启动策略

std::async 支持两种启动策略:

策略含义
std::launch::async强制在新线程上异步执行
std::launch::deferred延迟执行,在调用 get() 或 wait() 时才同步执行
std::launch::async | std::launch::deferred默认策略,由标准库实现决定
// 强制异步执行
auto fut1 = std::async(std::launch::async, task);

// 延迟执行(惰性求值)
auto fut2 = std::async(std::launch::deferred, task);

// 默认策略(实现决定)
auto fut3 = std::async(task);

3.3 关键注意事项

  • 默认策略的不确定性:使用默认策略时,无法确定任务是否真的在新线程上执行。如果依赖线程并发,应显式指定 std::launch::async。
  • future 析构会阻塞:当 std::future 析构时,如果任务尚未完成且采用 std::launch::async 策略,析构函数会阻塞等待任务完成。这在某些场景下可能造成意外阻塞。
  • 适合获取返回值:相比于 std::thread,std::async 更方便地获取任务结果和异常。

3.4 适用场景

  • 需要获取异步任务的返回值或捕获异常。
  • 任务相对独立,不需要精细控制线程生命周期。
  • 希望代码简洁,避免手动管理 std::thread 和共享状态。

4. std::jthread:自动 join 的现代线程

4.1 基本用法

构造函数原型

// C++20 起
template<class Function, class... Args>
explicit jthread(Function&& f, Args&&... args);

// C++20 起:接受停止令牌版本
template<class Function, class... Args>
explicit jthread(Function&& f, Args&&... args);

// 移动构造(C++20 起)
jthread(jthread&& other) noexcept;

// 拷贝构造被删除(不可复制)
jthread(const jthread&) = delete;
  • f:要在线程中执行的可调用对象。
  • args...:传递给 f 的参数。
  • 若 f 的第一个参数类型为 std::stop_token,则自动传入内部停止令牌,供协作式取消使用。
  • 析构时自动调用 join(),无需手动管理。

std::jthread 是 C++20 引入的线程类,全称是 “joining thread”。它的核心优势是析构时自动调用 join(),避免了忘记 join() 导致程序终止的问题。

#include <iostream>
#include <thread>

int main() {
    std::jthread t([] {
        std::cout << "Hello from jthread" << std::endl;
    });
    // 无需手动调用 join(),析构时自动 join
    return 0;
}

4.2 协作式取消

std::jthread 还引入了**停止令牌(stop token)**机制,支持协作式取消。通过 std::stop_token,可以在外部请求线程停止,线程内部可以响应这个请求。

#include <iostream>
#include <thread>
#include <chrono>

int main() {
    std::jthread t([](std::stop_token st) {
        while (!st.stop_requested()) {
            std::cout << "Working..." << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
        }
        std::cout << "Stopped." << std::endl;
    });

    std::this_thread::sleep_for(std::chrono::seconds(1));
    t.request_stop();   // 请求停止
    return 0;           // 析构时自动 join
}

4.3 关键注意事项

  • 需要 C++20 支持:std::jthread 是 C++20 标准的一部分,需要编译器支持 C++20。
  • 自动 join 的代价:析构时自动 join() 意味着如果线程还在运行,析构会阻塞等待。如果希望线程在后台运行,仍需显式 detach()(但 jthread 的 detach() 会失去停止令牌能力)。
  • 停止令牌是协作式的:request_stop() 只是发出请求,线程内部必须检查 stop_requested() 才能实际停止。如果线程在阻塞调用中(如 sleep_for),需要配合条件变量或 wait 接口才能及时响应。

4.4 适用场景

  • 希望避免忘记 join() 导致的程序崩溃。
  • 需要协作式取消机制,优雅地停止后台任务。
  • 使用 C++20 及以上的项目。

5. 三种方式的对比

特性std::threadstd::asyncstd::jthread
引入版本C++11C++11C++20
获取返回值不支持(需手动共享状态)支持(通过 future)不支持(需手动共享状态)
自动 join否(需手动调用)不适用是(析构时自动)
协作式取消不支持不支持支持(stop_token)
生命周期控制完全手动由 future 管理自动 join
异常传递线程内需自行处理通过 future 传递线程内需自行处理
适用场景精细控制线程异步任务 + 返回值安全便捷的后台任务

6. 选择时机建议

6.1 优先选择 std::jthread(C++20 项目)

如果你的项目使用 C++20,且不需要获取返回值,优先使用 std::jthread。它自动 join() 的特性极大降低了因忘记 join() 导致程序崩溃的风险,内置的停止令牌也让线程的优雅退出变得简单。

std::jthread worker([](std::stop_token st) {
    while (!st.stop_requested()) {
        // 处理任务
    }
});

6.2 需要返回值时选择 std::async

如果任务需要返回结果,或者希望异常能安全地传递到主线程,std::async 是最简洁的选择。它把线程管理和结果获取封装在一起,代码更清晰。

std::future<int> fut = std::async(std::launch::async, [] {
    return heavy_computation();
});
int result = fut.get();

6.3 需要精细控制时选择 std::thread

当需要精确控制线程的创建时机、生命周期、分离策略,或者需要管理线程池、设置线程优先级等底层操作时,std::thread 仍然是最灵活的选择。但务必记得调用 join() 或 detach()。

std::thread t(worker);
// 精细控制:决定何时 join 或 detach
t.join();

7. 总结

  • std::thread 是最基础的线程类,灵活但需要手动管理生命周期。
  • std::async 适合需要获取返回值或传递异常的异步任务。
  • std::jthread 是 C++20 的现代选择,自动 join() 并支持协作式取消。

在实际开发中,优先考虑 std::jthread(C++20 项目)或 std::async(需要返回值),只有在需要精细控制时才使用 std::thread。合理选择线程创建方式,能让代码更安全、更简洁、更易维护。

以上就是C++创建线程的几种方法详解的详细内容,更多关于C++线程创建方法的资料请关注脚本之家其它相关文章!

相关文章

  • C语言实现ATM系统程序的完整代码

    C语言实现ATM系统程序的完整代码

    这篇文章主要介绍了C语言实现ATM系统程序的完整代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • opencv3/C++ FLANN特征匹配方式

    opencv3/C++ FLANN特征匹配方式

    今天小编就为大家分享一篇opencv3/C++ FLANN特征匹配方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • 关于c语言中回调函数的理解

    关于c语言中回调函数的理解

    这篇文章主要给大家介绍了关于c语言中回调函数的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • C语言中时间的基本用法小结

    C语言中时间的基本用法小结

    处理时间是编程中经常遇到的问题,C语言中提供了一些时间处理函数,在此记录下一些基本的用法。下面这篇文章主要给大家介绍了C语言中关于时间的基本用法的相关资料,需要的朋友可以参考借鉴,感兴趣的朋友们来一起看看吧。
    2017-01-01
  • C/C++ chrono简单使用场景示例详解

    C/C++ chrono简单使用场景示例详解

    这篇文章主要介绍了C/C++ chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-06-06
  • C语言中进程信号集的相关操作函数详解

    C语言中进程信号集的相关操作函数详解

    这篇文章主要介绍了C语言中进程信号集的相关操作函数详解,包括sigismember函数和sigfillset函数以及sigemptyset函数的用法,需要的朋友可以参考下
    2015-09-09
  • C++ JSON库 nlohmann::basic_json::array 的用法示例详解

    C++ JSON库 nlohmann::basic_json::array 的用法示例详解

    nlohmann::json是一个C++的JSON库,它提供了一种容易和直观的方法来处理JSON数据,nlohmann::json::array()是用来创建一个JSON数组的方法,这篇文章主要介绍了C++ JSON库nlohmann::basic_json::array的用法,需要的朋友可以参考下
    2023-06-06
  • 深入解析 C++中std::stoul 函数

    深入解析 C++中std::stoul 函数

    std::stoul是 C++ 标准库中的一个字符串转换函数,它用于将 std::string 或 std::wstring 转换为 unsigned long 类型的整数,下面就来介绍一下
    2025-04-04
  • C++中拷贝构造函数的总结详解

    C++中拷贝构造函数的总结详解

    深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
    2013-09-09
  • OpenMP中For Construct对dynamic的调度方式详解

    OpenMP中For Construct对dynamic的调度方式详解

    在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,与他相关的动态库函数分析以及对 dynamic 的调度方式进行分析,希望对大家有所帮助
    2023-02-02

最新评论