C++ STL之std::thread与异步编程详解

C++ STL之std::thread与异步编程详解

一、用法速查

1.1 std::thread------基础线程

std::thread 是 C++11 引入的线程抽象,构造即启动,需在销毁前明确 join 或 detach ,否则析构时触发 std::terminate

cpp 复制代码
#include <thread>
#include <iostream>

void work(int id) {
    std::cout << "thread " << id << " running\n";
}

int main() {
    std::thread t(work, 1);
    t.join();                    // 等待线程结束(必须调用)
    // t.detach();               // 或者分离(后台运行)
    return 0;
}

RAII 包装(推荐):用对象生命周期保证 join:

cpp 复制代码
class ThreadGuard {
    std::thread t;
public:
    explicit ThreadGuard(std::thread t_) : t(std::move(t_)) {}
    ~ThreadGuard() { if (t.joinable()) t.join(); }
    ThreadGuard(const ThreadGuard&) = delete;
    ThreadGuard& operator=(const ThreadGuard&) = delete;
};

1.2 std::async------异步任务

std::async 返回 std::future,自动管理线程生命周期:

cpp 复制代码
#include <future>

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

int main() {
    // launch::async → 立即新线程执行
    auto f1 = std::async(std::launch::async, calc, 10);

    // launch::deferred → 惰性求值,get() 时才同步执行
    auto f2 = std::async(std::launch::deferred, calc, 20);

    // 默认策略(= async | deferred)由实现自行选择
    auto f3 = std::async(calc, 30);

    std::cout << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
    return 0;
}

1.3 std::future / std::shared_future------获取结果

future 只能 get() 一次;shared_future 可多次获取。

cpp 复制代码
std::promise<int> p;
auto f = p.get_future();

std::thread t([&p] { p.set_value(42); });
std::cout << f.get() << '\n';   // 阻塞直到拿到值
t.join();

1.4 std::packaged_task------包装可调用对象

将函数包装为异步任务,与 future 关联:

cpp 复制代码
std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
auto fut = task.get_future();

std::thread t(std::move(task), 3, 4);
t.join();
std::cout << fut.get() << '\n'; // 7

二、底层原理

2.1 future-promise 值通道

promisefuture 共享一个内部状态(shared state),通过条件变量实现线程间同步:
#mermaid-svg-ggyFLBPpYc86OOGd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ggyFLBPpYc86OOGd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ggyFLBPpYc86OOGd .error-icon{fill:#552222;}#mermaid-svg-ggyFLBPpYc86OOGd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ggyFLBPpYc86OOGd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ggyFLBPpYc86OOGd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ggyFLBPpYc86OOGd .marker.cross{stroke:#333333;}#mermaid-svg-ggyFLBPpYc86OOGd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ggyFLBPpYc86OOGd p{margin:0;}#mermaid-svg-ggyFLBPpYc86OOGd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ggyFLBPpYc86OOGd .cluster-label text{fill:#333;}#mermaid-svg-ggyFLBPpYc86OOGd .cluster-label span{color:#333;}#mermaid-svg-ggyFLBPpYc86OOGd .cluster-label span p{background-color:transparent;}#mermaid-svg-ggyFLBPpYc86OOGd .label text,#mermaid-svg-ggyFLBPpYc86OOGd span{fill:#333;color:#333;}#mermaid-svg-ggyFLBPpYc86OOGd .node rect,#mermaid-svg-ggyFLBPpYc86OOGd .node circle,#mermaid-svg-ggyFLBPpYc86OOGd .node ellipse,#mermaid-svg-ggyFLBPpYc86OOGd .node polygon,#mermaid-svg-ggyFLBPpYc86OOGd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ggyFLBPpYc86OOGd .rough-node .label text,#mermaid-svg-ggyFLBPpYc86OOGd .node .label text,#mermaid-svg-ggyFLBPpYc86OOGd .image-shape .label,#mermaid-svg-ggyFLBPpYc86OOGd .icon-shape .label{text-anchor:middle;}#mermaid-svg-ggyFLBPpYc86OOGd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ggyFLBPpYc86OOGd .rough-node .label,#mermaid-svg-ggyFLBPpYc86OOGd .node .label,#mermaid-svg-ggyFLBPpYc86OOGd .image-shape .label,#mermaid-svg-ggyFLBPpYc86OOGd .icon-shape .label{text-align:center;}#mermaid-svg-ggyFLBPpYc86OOGd .node.clickable{cursor:pointer;}#mermaid-svg-ggyFLBPpYc86OOGd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ggyFLBPpYc86OOGd .arrowheadPath{fill:#333333;}#mermaid-svg-ggyFLBPpYc86OOGd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ggyFLBPpYc86OOGd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ggyFLBPpYc86OOGd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ggyFLBPpYc86OOGd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ggyFLBPpYc86OOGd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ggyFLBPpYc86OOGd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ggyFLBPpYc86OOGd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ggyFLBPpYc86OOGd .cluster text{fill:#333;}#mermaid-svg-ggyFLBPpYc86OOGd .cluster span{color:#333;}#mermaid-svg-ggyFLBPpYc86OOGd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ggyFLBPpYc86OOGd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ggyFLBPpYc86OOGd rect.text{fill:none;stroke-width:0;}#mermaid-svg-ggyFLBPpYc86OOGd .icon-shape,#mermaid-svg-ggyFLBPpYc86OOGd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ggyFLBPpYc86OOGd .icon-shape p,#mermaid-svg-ggyFLBPpYc86OOGd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ggyFLBPpYc86OOGd .icon-shape .label rect,#mermaid-svg-ggyFLBPpYc86OOGd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ggyFLBPpYc86OOGd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ggyFLBPpYc86OOGd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ggyFLBPpYc86OOGd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} set_value / set_exception
get() 等待并读取
promise

(生产者)
共享状态

shared state
future

(消费者)
线程 A
线程 B

  • 生产者调用 set_value() 时,共享状态标记 ready 并唤醒等待者。
  • 消费者调用 get() 时,若未 ready 则阻塞在条件变量上。
  • future 析构时释放共享状态,shared_future 允许多读。

2.2 async 两种启动策略

#mermaid-svg-ug7udNyjqz45qB4X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ug7udNyjqz45qB4X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ug7udNyjqz45qB4X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ug7udNyjqz45qB4X .error-icon{fill:#552222;}#mermaid-svg-ug7udNyjqz45qB4X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ug7udNyjqz45qB4X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ug7udNyjqz45qB4X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ug7udNyjqz45qB4X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ug7udNyjqz45qB4X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ug7udNyjqz45qB4X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ug7udNyjqz45qB4X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ug7udNyjqz45qB4X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ug7udNyjqz45qB4X .marker.cross{stroke:#333333;}#mermaid-svg-ug7udNyjqz45qB4X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ug7udNyjqz45qB4X p{margin:0;}#mermaid-svg-ug7udNyjqz45qB4X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ug7udNyjqz45qB4X .cluster-label text{fill:#333;}#mermaid-svg-ug7udNyjqz45qB4X .cluster-label span{color:#333;}#mermaid-svg-ug7udNyjqz45qB4X .cluster-label span p{background-color:transparent;}#mermaid-svg-ug7udNyjqz45qB4X .label text,#mermaid-svg-ug7udNyjqz45qB4X span{fill:#333;color:#333;}#mermaid-svg-ug7udNyjqz45qB4X .node rect,#mermaid-svg-ug7udNyjqz45qB4X .node circle,#mermaid-svg-ug7udNyjqz45qB4X .node ellipse,#mermaid-svg-ug7udNyjqz45qB4X .node polygon,#mermaid-svg-ug7udNyjqz45qB4X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ug7udNyjqz45qB4X .rough-node .label text,#mermaid-svg-ug7udNyjqz45qB4X .node .label text,#mermaid-svg-ug7udNyjqz45qB4X .image-shape .label,#mermaid-svg-ug7udNyjqz45qB4X .icon-shape .label{text-anchor:middle;}#mermaid-svg-ug7udNyjqz45qB4X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ug7udNyjqz45qB4X .rough-node .label,#mermaid-svg-ug7udNyjqz45qB4X .node .label,#mermaid-svg-ug7udNyjqz45qB4X .image-shape .label,#mermaid-svg-ug7udNyjqz45qB4X .icon-shape .label{text-align:center;}#mermaid-svg-ug7udNyjqz45qB4X .node.clickable{cursor:pointer;}#mermaid-svg-ug7udNyjqz45qB4X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ug7udNyjqz45qB4X .arrowheadPath{fill:#333333;}#mermaid-svg-ug7udNyjqz45qB4X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ug7udNyjqz45qB4X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ug7udNyjqz45qB4X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ug7udNyjqz45qB4X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ug7udNyjqz45qB4X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ug7udNyjqz45qB4X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ug7udNyjqz45qB4X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ug7udNyjqz45qB4X .cluster text{fill:#333;}#mermaid-svg-ug7udNyjqz45qB4X .cluster span{color:#333;}#mermaid-svg-ug7udNyjqz45qB4X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ug7udNyjqz45qB4X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ug7udNyjqz45qB4X rect.text{fill:none;stroke-width:0;}#mermaid-svg-ug7udNyjqz45qB4X .icon-shape,#mermaid-svg-ug7udNyjqz45qB4X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ug7udNyjqz45qB4X .icon-shape p,#mermaid-svg-ug7udNyjqz45qB4X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ug7udNyjqz45qB4X .icon-shape .label rect,#mermaid-svg-ug7udNyjqz45qB4X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ug7udNyjqz45qB4X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ug7udNyjqz45qB4X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ug7udNyjqz45qB4X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} launch::async
launch::deferred
std::async 调用
启动策略
在新线程上

立即执行
存入共享状态

延迟执行
future.get()

可能直接返回
future.get()

触发同步计算
销毁共享状态

launch::async 创建独立线程立即执行;launch::deferred 仅在 get()wait()在当前线程同步调用------即惰性求值。

2.3 线程池原理与实现骨架

每次新建 std::thread 涉及系统调用,线程池通过复用线程减少开销。核心组件:

  1. 任务队列std::queue<std::packaged_task<void()>>
  2. 工作线程:循环从队列取任务执行
  3. 同步原语std::mutex + std::condition_variable
  4. 停止标志std::atomic<bool>
cpp 复制代码
class ThreadPool {
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex mtx;
    std::condition_variable cv;
    bool stop = false;

public:
    explicit ThreadPool(size_t n) {
        for (size_t i = 0; i < n; ++i)
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock lock(mtx);
                        cv.wait(lock, [this] { return stop || !tasks.empty(); });
                        if (stop && tasks.empty()) return;
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
    }

    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
        using Ret = decltype(f(args...));
        auto pkg = std::make_shared<std::packaged_task<Ret()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        auto res = pkg->get_future();
        {
            std::lock_guard lock(mtx);
            tasks.emplace([pkg] { (*pkg)(); });
        }
        cv.notify_one();
        return res;
    }

    ~ThreadPool() {
        {
            std::lock_guard lock(mtx);
            stop = true;
        }
        cv.notify_all();
        for (auto& w : workers) w.join();
    }
};

三、面试题

Q1:std::thread 析构时如果既没 join 也没 detach 会怎样?

调用 std::terminate。C++ 标准强制要求销毁前必须明确线程的处理方式,防止资源泄漏。

Q2:std::async 默认策略是什么?有什么陷阱?

默认是 launch::async | launch::deferred,由实现自行选择。陷阱:某些实现(如 MSVC)对默认策略启用延迟求值 ,导致 future 析构时阻塞等待结果("阻塞析构"),可能引发死锁。

Q3:std::promisestd::packaged_task 的异同?

两者都通过共享状态连接生产者与消费者。promise值通道 ------手动 set_value/set_exception;packaged_task函数包装------自动把返回值或异常写入共享状态。

Q4:std::future 为什么只能 get() 一次?

get() 内部将共享状态的所有权转移给调用者(move 语义),调用后 valid() == false。需要多次取值的场景用 std::shared_future

Q5:线程池为什么比每次 new thread 快?

系统线程创建/销毁涉及内核态切换、栈分配和调度器注册,每次约数微秒。线程池复用 N 个工作线程,消除重复创建开销,且通过任务队列削峰填谷,避免线程风暴。

Q6:std::launch::deferred 的典型应用场景?

惰性求值。当计算结果可能不会被用到(如条件分支中的一路),或需要将计算推迟到确定线程上下文中时,deferred 策略避免不必要的线程开销。

Q7:notify_onenotify_all 的区别?线程池中为什么 stop 时用 notify_all

notify_one 唤醒一个等待线程;notify_all 唤醒全部。线程池 stop 时所有工作线程都应退出,必须用 notify_all,否则部分线程可能永久阻塞。