C++20 线程返回值处理指南

C++20 线程返回值处理指南

1. 概述

在C++20中,线程返回值的处理变得更加安全和高效。C++20引入了std::jthread等新特性,结合传统的std::futurestd::promise等机制,可以灵活地处理各种场景下的线程返回值。

本文将详细介绍C++20中接收线程返回值的几种主要方式,并补充完善适用场景,包括大型计算、图像处理等实际应用。

2. 主要方法

2.1 使用 std::async

基本原理

std::async 是C++11引入的异步执行机制,在C++20中继续保持稳定。它会异步执行函数,并返回一个std::future对象,通过该对象可以获取函数的返回值。

代码示例
cpp 复制代码
#include <iostream>
#include <future>
#include <chrono>

int compute(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return a + b;
}

int main() {
    // C++20中std::async的行为保持不变
    std::future<int> result = std::async(compute, 10, 20);
    
    std::cout << "主线程继续执行..." << std::endl;
    
    int sum = result.get(); // 阻塞直到结果可用
    std::cout << "计算结果: " << sum << std::endl;
    
    return 0;
}
适用场景
  • 简单异步任务:不需要精细控制线程行为的场景
  • 轻量级计算:如简单的数值计算、数据转换等
  • 快速响应:需要主线程保持响应的UI应用
  • 临时任务:只需要执行一次的短期任务
大型计算应用
cpp 复制代码
#include <iostream>
#include <future>
#include <vector>
#include <numeric>

// 大型数组求和(模拟大型计算)
double large_computation(const std::vector<double>& data) {
    // 模拟耗时计算
    std::this_thread::sleep_for(std::chrono::seconds(3));
    
    // 实际计算:计算数组平均值
    double sum = std::accumulate(data.begin(), data.end(), 0.0);
    return sum / data.size();
}

int main() {
    // 生成大型数据
    const int DATA_SIZE = 100000000;
    std::vector<double> data(DATA_SIZE);
    for (int i = 0; i < DATA_SIZE; ++i) {
        data[i] = static_cast<double>(i) / DATA_SIZE;
    }
    
    std::cout << "启动大型计算任务..." << std::endl;
    auto start_time = std::chrono::high_resolution_clock::now();
    
    // 使用std::async执行大型计算
    std::future<double> result = std::async(large_computation, data);
    
    // 主线程可以继续执行其他任务
    std::cout << "主线程继续处理其他事务..." << std::endl;
    
    // 获取计算结果
    double average = result.get();
    
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
    
    std::cout << "计算完成!" << std::endl;
    std::cout << "平均值: " << average << std::endl;
    std::cout << "耗时: " << duration.count() << "秒" << std::endl;
    
    return 0;
}

2.2 使用 std::promise + std::jthread

基本原理

结合std::promisestd::jthread,可以在一个线程中设置值,在另一个线程中获取该值。std::jthread的自动管理特性避免了手动join()的繁琐。

代码示例
cpp 复制代码
#include <iostream>
#include <thread>    // 包含std::jthread
#include <future>
#include <chrono>

// 线程函数接收promise右值引用
void compute(std::promise<int>&& prom, int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    prom.set_value(a + b);
}

int main() {
    std::promise<int> prom;
    std::future<int> result = prom.get_future();
    
    // 使用std::jthread,自动管理生命周期,无需手动join()
    std::jthread t(compute, std::move(prom), 10, 20);
    
    std::cout << "主线程继续执行..." << std::endl;
    
    int sum = result.get();
    std::cout << "计算结果: " << sum << std::endl;
    
    // t自动join,无需手动调用
    return 0;
}
适用场景
  • 复杂异步流程:需要分步设置返回值的场景
  • 长时间运行的任务:如服务器请求、网络通信等
  • 需要异常处理 :可以使用promise.set_exception()传递异常
  • 多阶段计算:需要分阶段返回结果的场景
图像处理应用
cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <vector>
#include <cstdint>

// 图像处理:高斯模糊
struct Image {
    int width;
    int height;
    std::vector<uint8_t> data;
    
    Image(int w, int h) : width(w), height(h), data(w * h * 3, 0) {}
};

// 高斯模糊处理
void gaussian_blur(std::promise<Image>&& prom, const Image& input, double sigma) {
    std::cout << "开始图像处理..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时处理
    
    // 创建输出图像
    Image output(input.width, input.height);
    
    // 模拟高斯模糊处理(实际实现会更复杂)
    for (int i = 0; i < input.data.size(); ++i) {
        // 简单的模糊算法:取周围像素平均值
        output.data[i] = (input.data[i] + 100) % 256;
    }
    
    std::cout << "图像处理完成" << std::endl;
    prom.set_value(std::move(output));
}

int main() {
    // 创建测试图像(1920x1080,RGB)
    const int WIDTH = 1920;
    const int HEIGHT = 1080;
    Image input(WIDTH, HEIGHT);
    
    // 填充测试数据
    for (int i = 0; i < input.data.size(); ++i) {
        input.data[i] = i % 256;
    }
    
    std::promise<Image> prom;
    std::future<Image> result = prom.get_future();
    
    // 启动图像处理线程
    std::jthread t(gaussian_blur, std::move(prom), input, 1.5);
    
    // 主线程可以继续执行其他任务
    std::cout << "主线程继续处理其他任务..." << std::endl;
    
    // 获取处理结果
    Image output = result.get();
    
    std::cout << "图像模糊处理完成!" << std::endl;
    std::cout << "输入图像大小: " << input.data.size() / (1024 * 1024) << " MB" << std::endl;
    std::cout << "输出图像大小: " << output.data.size() / (1024 * 1024) << " MB" << std::endl;
    
    return 0;
}

2.3 使用共享变量 + std::jthread

基本原理

通过共享变量传递返回值,结合C++20的std::jthread和同步机制(如std::mutex)确保线程安全。

代码示例
cpp 复制代码
#include <iostream>
#include <thread>    // 包含std::jthread
#include <mutex>
#include <chrono>

int result = 0;
std::mutex mtx;

void compute(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    int sum = a + b;
    
    // 互斥锁保护共享变量
    std::lock_guard<std::mutex> lock(mtx);
    result = sum;
}

int main() {
    // 使用std::jthread
    std::jthread t(compute, 10, 20);
    
    std::cout << "主线程继续执行..." << std::endl;
    
    // t自动join,确保线程结束后再访问result
    int sum = result;
    std::cout << "计算结果: " << sum << std::endl;
    
    return 0;
}
适用场景
  • 简单数据共享:只需要传递少量数据的场景
  • 频繁更新:需要多次更新返回值的场景
  • ** legacy代码迁移**:从旧代码迁移到C++20的过渡方案
  • 性能敏感场景:避免future/promise的额外开销
大型矩阵计算应用
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <vector>

// 矩阵类型
typedef std::vector<std::vector<double>> Matrix;

// 全局结果矩阵和互斥锁
Matrix result_matrix;
std::mutex mtx;

// 矩阵乘法
void multiply_matrices(const Matrix& A, const Matrix& B, int start_row, int end_row) {
    int n = A.size();
    int m = B[0].size();
    int k = B.size();
    
    std::cout << "线程开始计算行 " << start_row << " 到 " << end_row << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟耗时计算
    
    // 执行矩阵乘法
    for (int i = start_row; i < end_row; ++i) {
        for (int j = 0; j < m; ++j) {
            double sum = 0.0;
            for (int l = 0; l < k; ++l) {
                sum += A[i][l] * B[l][j];
            }
            
            // 保护共享结果矩阵
            std::lock_guard<std::mutex> lock(mtx);
            result_matrix[i][j] = sum;
        }
    }
    
    std::cout << "线程完成计算行 " << start_row << " 到 " << end_row << std::endl;
}

int main() {
    // 创建大型矩阵(1000x1000)
    const int SIZE = 1000;
    Matrix A(SIZE, std::vector<double>(SIZE, 1.0));
    Matrix B(SIZE, std::vector<double>(SIZE, 2.0));
    
    // 初始化结果矩阵
    result_matrix.resize(SIZE, std::vector<double>(SIZE, 0.0));
    
    // 使用4个线程并行计算
    const int NUM_THREADS = 4;
    const int ROWS_PER_THREAD = SIZE / NUM_THREADS;
    
    std::vector<std::jthread> threads;
    
    auto start_time = std::chrono::high_resolution_clock::now();
    
    // 创建并启动线程
    for (int i = 0; i < NUM_THREADS; ++i) {
        int start = i * ROWS_PER_THREAD;
        int end = (i == NUM_THREADS - 1) ? SIZE : (i + 1) * ROWS_PER_THREAD;
        threads.emplace_back(multiply_matrices, std::ref(A), std::ref(B), start, end);
    }
    
    // 等待所有线程完成(jthread自动join)
    
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
    
    std::cout << "矩阵乘法完成!" << std::endl;
    std::cout << "矩阵大小: " << SIZE << "x" << SIZE << std::endl;
    std::cout << "使用线程数: " << NUM_THREADS << std::endl;
    std::cout << "耗时: " << duration.count() << "秒" << std::endl;
    
    // 验证结果(左上角元素应为SIZE*2.0)
    std::cout << "结果验证:result[0][0] = " << result_matrix[0][0] << "(预期:" << SIZE * 2.0 << ")" << std::endl;
    
    return 0;
}

2.4 使用 std::packaged_task + std::jthread

基本原理

std::packaged_task 将可调用对象包装起来,结合std::jthread可以灵活地处理线程返回值。

代码示例
cpp 复制代码
#include <iostream>
#include <thread>    // 包含std::jthread
#include <future>
#include <chrono>

int compute(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return a + b;
}

int main() {
    // 创建packaged_task包装compute函数
    std::packaged_task<int(int, int)> task(compute);
    std::future<int> result = task.get_future();
    
    // 使用std::jthread执行task,自动管理生命周期
    std::jthread t(std::move(task), 10, 20);
    
    std::cout << "主线程继续执行..." << std::endl;
    
    int sum = result.get();
    std::cout << "计算结果: " << sum << std::endl;
    
    // t自动join,无需手动调用
    return 0;
}
适用场景
  • 可重用任务:需要多次执行相同任务的场景
  • 函数对象包装:方便包装各种可调用对象
  • 延迟执行:可以在需要时才执行任务
  • 任务队列:适合实现线程池等复杂场景
图像处理流水线应用
cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <vector>
#include <cstdint>

// 图像结构
struct Image {
    int width;
    int height;
    std::vector<uint8_t> data;
    
    Image(int w, int h) : width(w), height(h), data(w * h * 3, 0) {}
};

// 图像处理步骤1:读取图像
Image load_image(const std::string& filename) {
    std::cout << "加载图像: " << filename << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    // 模拟加载图像
    Image img(1920, 1080);
    for (int i = 0; i < img.data.size(); ++i) {
        img.data[i] = i % 256;
    }
    
    return img;
}

// 图像处理步骤2:灰度转换
Image to_grayscale(const Image& img) {
    std::cout << "转换为灰度图像" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    Image gray(img.width, img.height);
    for (int i = 0; i < img.data.size(); i += 3) {
        // 灰度转换公式:Y = 0.299*R + 0.587*G + 0.114*B
        uint8_t gray_val = static_cast<uint8_t>(
            0.299 * img.data[i] + 
            0.587 * img.data[i+1] + 
            0.114 * img.data[i+2]
        );
        gray.data[i] = gray_val;
        gray.data[i+1] = gray_val;
        gray.data[i+2] = gray_val;
    }
    
    return gray;
}

// 图像处理步骤3:边缘检测
Image edge_detection(const Image& img) {
    std::cout << "执行边缘检测" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    Image edges(img.width, img.height);
    // 简单的边缘检测算法(模拟)
    for (int i = 0; i < img.data.size(); ++i) {
        edges.data[i] = (img.data[i] > 128) ? 255 : 0;
    }
    
    return edges;
}

int main() {
    std::string filename = "test.jpg";
    
    // 创建三个packaged_task,分别对应图像处理的三个步骤
    std::packaged_task<Image(const std::string&)> load_task(load_image);
    std::packaged_task<Image(const Image&)> gray_task(to_grayscale);
    std::packaged_task<Image(const Image&)> edge_task(edge_detection);
    
    // 获取对应的future
    std::future<Image> load_future = load_task.get_future();
    std::future<Image> gray_future = gray_task.get_future();
    std::future<Image> edge_future = edge_task.get_future();
    
    auto start_time = std::chrono::high_resolution_clock::now();
    
    // 启动第一个任务:加载图像
    std::jthread load_thread(std::move(load_task), filename);
    
    // 等待图像加载完成
    Image img = load_future.get();
    
    // 启动第二个任务:灰度转换
    std::jthread gray_thread(std::move(gray_task), img);
    
    // 等待灰度转换完成
    Image gray = gray_future.get();
    
    // 启动第三个任务:边缘检测
    std::jthread edge_thread(std::move(edge_task), gray);
    
    // 等待边缘检测完成
    Image edges = edge_future.get();
    
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
    
    std::cout << "图像处理流水线完成!" << std::endl;
    std::cout << "图像大小: " << img.width << "x" << img.height << std::endl;
    std::cout << "总耗时: " << duration.count() << "秒" << std::endl;
    
    return 0;
}

2.5 C++20 新增:结合 std::stop_token

基本原理

C++20的std::jthread内置了std::stop_source,支持协作式停止。结合std::promise可以实现既支持停止请求又能返回结果的线程。

代码示例
cpp 复制代码
#include <iostream>
#include <thread>    // 包含std::jthread和stop_token
#include <future>
#include <chrono>

// 线程函数接收stop_token和promise
void compute(std::stop_token st, std::promise<int>&& prom, int a, int b) {
    for (int i = 0; i < 5; ++i) {
        // 检查是否收到停止请求
        if (st.stop_requested()) {
            prom.set_value(-1); // 返回特殊值表示被中断
            return;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(400));
    }
    prom.set_value(a + b);
}

int main() {
    std::promise<int> prom;
    std::future<int> result = prom.get_future();
    
    // 创建jthread,自动管理stop_source
    std::jthread t(compute, t.get_stop_token(), std::move(prom), 10, 20);
    
    std::cout << "主线程等待1秒后请求停止..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    // 请求停止线程
    t.request_stop();
    
    int sum = result.get();
    if (sum == -1) {
        std::cout << "计算被中断" << std::endl;
    } else {
        std::cout << "计算结果: " << sum << std::endl;
    }
    
    // t自动join
    return 0;
}
适用场景
  • 可中断的长时间任务:如大型计算、数据分析等
  • 用户可取消的操作:如用户点击取消按钮的场景
  • 资源密集型任务:需要根据系统负载动态调整的任务
  • 超时控制:结合超时机制实现任务超时终止
大型数据挖掘应用
cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <vector>
#include <random>

// 数据挖掘:寻找最大数值
void find_max_value(std::stop_token st, std::promise<double>&& prom, const std::vector<double>& data) {
    double max_val = -std::numeric_limits<double>::infinity();
    
    std::cout << "开始数据挖掘..." << std::endl;
    
    for (size_t i = 0; i < data.size(); ++i) {
        // 每处理10000个元素检查一次停止请求
        if (i % 10000 == 0 && st.stop_requested()) {
            std::cout << "数据挖掘被中断,已处理 " << i << " 个元素" << std::endl;
            prom.set_value(max_val); // 返回当前找到的最大值
            return;
        }
        
        if (data[i] > max_val) {
            max_val = data[i];
        }
        
        // 模拟复杂计算
        std::this_thread::sleep_for(std::chrono::nanoseconds(10));
    }
    
    std::cout << "数据挖掘完成" << std::endl;
    prom.set_value(max_val);
}

int main() {
    // 生成1亿个随机数
    const size_t DATA_SIZE = 100000000;
    std::vector<double> data(DATA_SIZE);
    
    // 填充随机数据
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<double> dist(0.0, 1000000.0);
    
    std::cout << "生成随机数据..." << std::endl;
    for (double& val : data) {
        val = dist(gen);
    }
    std::cout << "随机数据生成完成" << std::endl;
    
    std::promise<double> prom;
    std::future<double> result = prom.get_future();
    
    // 创建jthread,支持协作式停止
    std::jthread t(find_max_value, t.get_stop_token(), std::move(prom), data);
    
    // 主线程等待2秒后请求停止
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    std::cout << "主线程请求停止数据挖掘" << std::endl;
    t.request_stop();
    
    // 获取结果
    double max_val = result.get();
    
    std::cout << "找到的最大值: " << max_val << std::endl;
    
    return 0;
}

3. 适用场景总结

3.1 大型计算

方法 适用场景 优势
std::async 简单的大型计算任务,如数值积分、统计分析 简单易用,自动管理
std::promise+std::jthread 复杂的分阶段计算,如有限元分析、蒙特卡洛模拟 支持分阶段返回结果,可处理异常
共享变量+std::jthread 高性能要求的大型矩阵计算、线性代数运算 避免额外开销,适合频繁更新
std::packaged_task+std::jthread 可重用的大型计算任务,如机器学习模型训练 支持任务复用,适合构建计算流水线
std::stop_token+std::promise 可中断的大型计算,如数据挖掘、优化算法 支持用户取消,资源利用率高

3.2 图像处理

方法 适用场景 优势
std::async 简单的图像处理任务,如格式转换、缩放 简单易用,适合单次处理
std::promise+std::jthread 复杂的图像处理算法,如深度学习推理、3D渲染 支持复杂流程,可处理异常
共享变量+std::jthread 实时图像处理,如视频流处理、摄像头捕获 低延迟,适合实时应用
std::packaged_task+std::jthread 图像处理流水线,如图像增强、目标检测 支持任务链,适合多步骤处理
std::stop_token+std::promise 大型图像批处理,如图像库管理、批量编辑 支持中断,适合长时间批处理

4. 编译和运行

4.1 编译命令

MSVC (Visual Studio 2022)
cmd 复制代码
cl /std:c++20 /EHsc your_file.cpp /Fe:your_program.exe
MinGW-w64
bash 复制代码
g++ -std=c++20 -pthread your_file.cpp -o your_program.exe
GCC (Linux)
bash 复制代码
g++ -std=c++20 -pthread your_file.cpp -o your_program

4.2 运行

bash 复制代码
# Windows
your_program.exe

# Linux/macOS
./your_program

5. 总结和选择建议

5.1 选择建议

  1. 优先使用 std::jthread:自动管理生命周期,避免线程泄漏
  2. 简单场景使用 std::async:代码简洁,适合快速开发
  3. 复杂场景使用 std::promise:灵活性高,支持分阶段返回
  4. 高性能场景使用共享变量:避免额外开销,适合频繁更新
  5. 可重用任务使用 std::packaged_task:支持任务复用,适合构建复杂系统
  6. 可中断任务使用 std::stop_token:支持协作式停止,提高资源利用率

5.2 最佳实践

  1. 始终检查 future 的有效性 :避免多次调用 get()
  2. 使用适当的同步机制:保护共享变量,避免数据竞争
  3. 考虑异常处理 :使用 promise.set_exception() 传递异常
  4. 合理设置线程数量:避免过多线程导致的上下文切换开销
  5. 结合 std::stop_token:为长时间任务提供取消机制
  6. 使用 std::ref 传递大型对象:避免不必要的拷贝

6. 结论

C++20为线程返回值处理提供了更加安全、高效的机制。结合std::jthreadstd::futurestd::promise等特性,可以灵活地处理各种场景下的线程返回值,从简单的数值计算到复杂的大型计算和图像处理。

通过选择合适的方法,并结合最佳实践,可以编写出更加健壮、高效的并发程序。在实际应用中,应根据具体场景选择最适合的线程返回值处理方式,以达到最佳的性能和可维护性。

相关推荐
凌乱风雨12115 天前
从源码角度解析C++20新特性如何简化线程超时取消
前端·算法·c++20
shuai132_13 天前
【无标题】
c++20
ULTRA??16 天前
基于range的函数式编程C++,python比较
c++·python·kotlin·c++20
apocelipes16 天前
从源码角度解析C++20新特性如何简化线程超时取消
c++·性能优化·golang·并发·c++20·linux编程
ALex_zry16 天前
C++20和C++23 在内存管理、并发控制和类型安全相关优化方式的详细技术分析
安全·c++20·c++23
ALex_zry16 天前
C++20/23标准对进程间共享信息的优化:从传统IPC到现代C++的演进
开发语言·c++·c++20
fpcc20 天前
c++20容器中的透明哈希
哈希算法·c++20
小老鼠不吃猫20 天前
C++20 STL <numbers> 数学常量库
开发语言·c++·c++20
Chrikk20 天前
C++20 Concepts 在算子库开发中的应用:从 SFINAE 到类型约束
人工智能·算法·c++20