C++并发编程:异步任务与线程池实战

本文是 C++ 系列教程的第 25 篇。上一篇讲解了条件变量与原子操作,本篇实战并发收尾:std::async 与 future、packaged_task 与 promise、线程池实现与任务调度,覆盖 9 个完整示例代码。

一、异步任务(std::async 与 std::future)

1.1 std::async 启动异步任务

std::async<future> 头文件提供,把「启动线程 + 获取结果」封装成一行。返回 std::future 对象,调用 get() 阻塞等待结果:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
using namespace std;

int computeSum(int n) {
    int sum = 0;
    for (int i = 1; i <= n; ++i) sum += i;
    return sum;
}

int main() {
    // 异步启动(默认策略可能开新线程或复用当前线程)
    future<int> f1 = async(launch::async, computeSum, 10000);
    future<int> f2 = async(launch::async, computeSum, 5000);

    // 主线程继续做别的事
    cout << "主线程工作中..." << endl;
    this_thread::sleep_for(chrono::milliseconds(100));

    // get() 阻塞直到结果就绪
    cout << "sum(1..10000) = " << f1.get() << endl;
    cout << "sum(1..5000) = " << f2.get() << endl;
    return 0;
}

launch::async 强制新线程执行;launch::deferred 惰性执行(get 时才在调用线程运行);不传则实现自行选择。

1.2 异常传递与 wait_for 超时

future::get() 会把异步任务中的异常重新抛出;wait_for 可非阻塞查询任务状态:

cpp 复制代码
#include <iostream>
#include <future>
#include <chrono>
#include <stdexcept>
using namespace std;

int riskyTask(int x) {
    if (x < 0) throw runtime_error("参数不能为负");
    this_thread::sleep_for(chrono::milliseconds(300));
    return x * 2;
}

int main() {
    future<int> f = async(launch::async, riskyTask, -5);

    // 等待最多 100ms,看是否完成
    auto status = f.wait_for(chrono::milliseconds(100));
    if (status == future_status::timeout)
        cout << "任务未完成,继续等待..." << endl;

    try {
        int result = f.get();          // 异常在此抛出
        cout << "结果: " << result << endl;
    } catch (const exception& e) {
        cout << "捕获异常: " << e.what() << endl;
    }
    return 0;
}

future_status 有三种:ready(已完成)、timeout(超时未完成)、deferred(惰性未启动)。

二、promise 与 packaged_task

2.1 std::promise 手动设置结果

std::promisestd::future 配对:promise 负责「生产结果」,future 负责「消费结果」。适合线程间传递单次计算结果:

cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <string>
using namespace std;

void worker(promise<string> p, string name) {
    this_thread::sleep_for(chrono::milliseconds(200));
    p.set_value("你好, " + name + "!");   // 生产结果
}

int main() {
    promise<string> prom;
    future<string> fut = prom.get_future();

    thread t(worker, move(prom), "张三");
    cout << "等待结果..." << endl;
    string result = fut.get();            // 阻塞直到 set_value
    cout << result << endl;
    t.join();
    return 0;
}

注意:promise 必须 move 进线程(不可拷贝)。set_value 只能调用一次,重复调用会抛 std::future_error

2.2 std::packaged_task 包装可调用对象

packaged_task 把「可调用对象 + future」打包,适合把任务交给线程池或队列调度:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
using namespace std;

int multiply(int a, int b) {
    return a
 * b;
}

int main() {
    packaged_task<int(int, int)> task(multiply);
    future<int> fut = task.get_future();

    thread t(move(task), 6, 7);   // 在线程中执行任务
    cout << "6 * 7 = " << fut.get() << endl;
    t.join();
    return 0;
}

packaged_taskstd::function 用法类似,区别是它自带 future 用于接收任务结果。

三、线程池实现

3.1 为什么要线程池

频繁创建/销毁线程开销大。线程池预创建固定数量线程,持续从任务队列取任务执行,大幅降低线程管理开销,也避免线程数无限增长。

3.2 完整线程池实现

综合运用互斥锁、条件变量、函数对象与任务队列:

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <future>
#include <vector>
using namespace std;

class ThreadPool {
    vector<thread> workers;
    queue<function<void()>> tasks;
    mutex mtx;
    condition_variable cv;
    bool stop;

public:
    explicit ThreadPool(size_t threads) : stop(false) {
        for (size_t i = 0; i < threads; ++i) {
            workers.emplace_back([this] {
              
  while (true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(mtx);
                        cv.wait(lock, [this] {
                            return stop || !tasks.empty();
                        });
                        if (stop && tasks.empty()) return;
                        task = move(tasks.front());
                        tasks.pop();
                    }
                    task();   // 在锁外执行,避免阻塞其他线程取任务
                }
            });
        }
    }

    // 提交任务,返回 future 获取结果
    template <typename F, typename... Args>
    auto enqueue(F&& f, Args&&... args)
        -> future<invoke_result_t<F, Args...>> {
        using Ret = invoke_result_t<F, Args...>;
        auto task = make_shared<packaged_task<Ret()>>(
            bind(forward<F>(f), forward<Args>(args)...));
        future<Ret> res = task->get_future();
        {
            lock_guard<mutex> lock(mtx);
            if (stop) throw runtime_error("线程池已停止");
            tasks.emplace([task]() { (*task)(); });
        }
        cv.notify_one();
        return res;
    }

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

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

int main() {
    ThreadPool pool(4);

    vector<future<int>> results;
    for (int i = 1; i <= 8; ++i) {
        results.push_back(pool.enqueue(square, i));
    }

    for (auto& f : results) {
        cout << f.get() << " ";
    }
    cout << endl;
    return 0;
}

核心设计:工作线程循环 cv.wait 等待任务;enqueuepackaged_task 包装任务并返回 future;析构时置 stopnotify_all,所有线程优雅退出。锁外执行任务避免串行化。

四、实战:并行归并排序

用线程池加速归并排序,体会「任务拆分 + 并行执行 + 结果合并」的完整异步模式:

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <future>
#include <vector>
#include <algorithm>
using namespace std;

// 简化版线程池(复用 3.2 的思路,此处直接递归拆分线程)
mutex outMtx;

void merge(vector<int>& arr,
 int left, int mid, int right) {
    vector<int> tmp(right - left + 1);
    int i = left, j = mid + 1, k = 0;
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
        else tmp[k++] = arr[j++];
    }
    while (i <= mid) tmp[k++] = arr[i++];
    while (j <= right) tmp[k++] = arr[j++];
    copy(tmp.begin(), tmp.end(), arr.begin() + left);
}

void parallelMergeSort(vector<int>& arr, int left, int right, int depth = 0) {
    if (left >= right) return;
    int mid = (left + right) / 2;

    if (depth < 3) {   // 深度限制,避免创建过多线程
        auto f1 = async(launch::async, [&] {
            parallelMergeSort(arr, left, mid, depth + 1);
        });
        auto f2 = async(launch::async, [&] {
            parallelMergeSort(arr, mid + 1, right, depth + 1);
        });
        f1.get();
        f2.get();
    } else {           // 深度足够时退化为串行
        parallelMergeSort(arr, left, mid, depth + 1);
        parallelMergeSort(arr, mid + 1, right, depth + 1);
    }
    merge(arr, left, mid, right);
}

int main() {
    vector<int> arr(100000);
    for (int i = 0; i < (int)arr.size(); ++i)
        arr[i] = rand() % 100000;

 
   auto begin = chrono::high_resolution_clock::now();
    parallelMergeSort(arr, 0, (int)arr.size() - 1);
    auto end = chrono::high_resolution_clock::now();
    double ms = chrono::duration<double, milli>(end - begin).count();

    bool sorted = is_sorted(arr.begin(), arr.end());
    cout << "排序完成: " << (sorted ? "是" : "否")
         << ",耗时 " << ms << " ms" << endl;
    return 0;
}

用深度限制控制并行度,避免 async 创建成百上千线程导致性能反降。深度 3 意味着最多 8 个并行段,配合归并保证正确合并。

总结

本篇系统讲解了 C++ 异步与线程池:std::async + future 一行启动异步任务并获取结果(含异常传递与 wait_for 超时)、promise/future 手动生产-消费单次结果、packaged_task 把任务与 future 打包、线程池的核心实现(条件变量任务队列 + packaged_task 封装 + 优雅关闭)、以及并行归并排序实战。至此 C++ 并发三篇(23-25)全部完成,涵盖锁、原子、条件变量、异步与线程池全链路。

下一篇进入 C++ 工程化:CMake 构建与多文件项目管理,敬请期待!

相关推荐
凉茶钱33 分钟前
【数据结构】计数排序
数据结构·算法·排序算法
心中有你021434 分钟前
Python爬虫实战:京东商品数据爬取+可视化分析
开发语言·爬虫·python
水饺编程2 小时前
第5章,[Win32 章节] :练习程序,BigFontPic
c语言·c++·windows·visual studio
不正经学生6 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
门思科技6 小时前
LoRaWAN 设备类别:Class A、B、C 对比与选型指南
c语言·开发语言·php
毕竟是shy哥9 小时前
计算YOLO数据集中每个类的目标数
算法·yolo·机器学习
M78佐菲9 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
恋恋西风9 小时前
C++ 理解 std::thread 在单核和多核上的行为差异
开发语言·c++
Brilliantwxx9 小时前
【Linux】 进程(9)程序与进程地址空间(基础+进阶+面试题)
linux·运维·服务器·开发语言·c++