C++20 线程返回值处理指南
1. 概述
在C++20中,线程返回值的处理变得更加安全和高效。C++20引入了std::jthread等新特性,结合传统的std::future、std::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::promise和std::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 选择建议
- 优先使用
std::jthread:自动管理生命周期,避免线程泄漏 - 简单场景使用
std::async:代码简洁,适合快速开发 - 复杂场景使用
std::promise:灵活性高,支持分阶段返回 - 高性能场景使用共享变量:避免额外开销,适合频繁更新
- 可重用任务使用
std::packaged_task:支持任务复用,适合构建复杂系统 - 可中断任务使用
std::stop_token:支持协作式停止,提高资源利用率
5.2 最佳实践
- 始终检查
future的有效性 :避免多次调用get() - 使用适当的同步机制:保护共享变量,避免数据竞争
- 考虑异常处理 :使用
promise.set_exception()传递异常 - 合理设置线程数量:避免过多线程导致的上下文切换开销
- 结合
std::stop_token:为长时间任务提供取消机制 - 使用
std::ref传递大型对象:避免不必要的拷贝
6. 结论
C++20为线程返回值处理提供了更加安全、高效的机制。结合std::jthread、std::future、std::promise等特性,可以灵活地处理各种场景下的线程返回值,从简单的数值计算到复杂的大型计算和图像处理。
通过选择合适的方法,并结合最佳实践,可以编写出更加健壮、高效的并发程序。在实际应用中,应根据具体场景选择最适合的线程返回值处理方式,以达到最佳的性能和可维护性。