C++ task
类型:异步操作的统一封装与组合 (讲座模式)
各位靓仔靓女,欢迎来到今天的C++异步编程小课堂!今天我们不聊虚的,直接上干货,主题就是C++11引入的task
类型。这玩意儿就像异步世界里的瑞士军刀,能把各种乱七八糟的异步操作收拾得服服帖帖,还能像搭积木一样组合起来,简直不要太方便!
为什么要用task
?
在没有task
之前,C++的异步编程简直就是一场噩梦。各种线程、锁、条件变量满天飞,代码写得比意大利面条还乱。而且,异常处理也是个大问题,一不小心就崩溃给你看。
task
的出现就是为了解决这些痛点。它可以:
- 统一封装异步操作: 把一个异步操作包装成一个
task
对象,就像把一团乱麻整理成一个整齐的线团。 - 简化异常处理:
task
会默默地把异步操作中的异常存起来,等你用get()
取结果的时候再抛出来,避免程序直接崩溃。 - 方便组合异步操作: 可以像搭积木一样,把多个
task
组合起来,形成更复杂的异步流程。
简单来说,task
就是让异步编程变得更简单、更安全、更可控。
task
的基本用法:把函数变成异步任务
最简单的用法就是用std::async
把一个函数变成一个task
。std::async
会自动把函数放到一个独立的线程里去执行,然后返回一个task
对象。
#include <iostream>
#include <future>
#include <thread>
int calculate_sum(int a, int b) {
std::cout << "Calculating sum in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return a + b;
}
int main() {
std::future<int> future_result = std::async(std::launch::async, calculate_sum, 10, 20);
std::cout << "Doing other stuff in main thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Getting result..." << std::endl;
int result = future_result.get(); // 阻塞,直到结果可用
std::cout << "Result: " << result << std::endl;
return 0;
}
代码解释:
std::async(std::launch::async, calculate_sum, 10, 20)
: 这行代码创建了一个task
,它会在一个新的线程里执行calculate_sum(10, 20)
。std::launch::async
表示强制在新线程中执行。future_result.get()
: 这行代码会阻塞,直到calculate_sum
执行完毕,并返回结果。如果calculate_sum
抛出异常,get()
也会抛出同样的异常。
注意事项:
std::launch::async
:强制在新线程中执行。std::launch::deferred
:延迟执行,直到调用get()
或wait()
。- 如果不指定
launch
策略,std::async
会根据系统资源情况自动选择。
task
的get()
方法:获取结果,处理异常
task
最核心的方法就是get()
。它会阻塞,直到异步操作完成,然后返回结果。如果异步操作抛出异常,get()
也会抛出同样的异常。
#include <iostream>
#include <future>
#include <stdexcept>
int might_throw() {
throw std::runtime_error("Something went wrong!");
}
int main() {
std::future<int> future_result = std::async(std::launch::async, might_throw);
try {
int result = future_result.get();
std::cout << "Result: " << result << std::endl; // 不会执行到这里
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
}
return 0;
}
代码解释:
might_throw()
: 这个函数会抛出一个std::runtime_error
异常。future_result.get()
: 这行代码会捕获might_throw()
抛出的异常,并重新抛出。
重点: task
会自动处理异常,避免程序直接崩溃。
task
的wait()
和wait_for()
方法:等待任务完成
有时候,我们不需要立即获取结果,只需要等待任务完成即可。这时候,就可以用wait()
和wait_for()
方法。
wait()
: 阻塞,直到任务完成。wait_for()
: 阻塞一段时间,如果任务在这段时间内完成,就返回std::future_status::ready
,否则返回std::future_status::timeout
。
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
int calculate_something() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
}
int main() {
std::future<int> future_result = std::async(std::launch::async, calculate_something);
std::cout << "Waiting for the task to complete..." << std::endl;
if (future_result.wait_for(std::chrono::seconds(2)) == std::future_status::timeout) {
std::cout << "Task timed out!" << std::endl;
} else {
std::cout << "Task completed!" << std::endl;
int result = future_result.get(); // Now it won't block
std::cout << "Result: " << result << std::endl;
}
return 0;
}
代码解释:
future_result.wait_for(std::chrono::seconds(2))
: 这行代码会阻塞最多2秒钟,等待calculate_something()
执行完毕。- 如果2秒钟后
calculate_something()
还没有执行完毕,wait_for()
会返回std::future_status::timeout
。
task
的组合:让异步操作像搭积木一样
task
最强大的地方在于它可以组合。我们可以把多个task
组合起来,形成更复杂的异步流程。
方法一:then()
(C++20)
C++20 引入了 then()
方法,让 task
的组合更加优雅。then()
允许你指定一个回调函数,当 task
完成后,回调函数会自动执行。
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
int calculate_square(int x) {
std::cout << "Calculating square in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * x;
}
int calculate_cube(int x) {
std::cout << "Calculating cube in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * x * x;
}
int main() {
std::future<int> future_square = std::async(std::launch::async, calculate_square, 5);
// C++20's then() requires a bit more setup, often with a shared_future
auto shared_future_square = future_square.share(); // Convert to shared_future
auto future_cube = std::async(std::launch::async, [shared_future_square]() {
int square = shared_future_square.get(); // Get the result of the square calculation
return calculate_cube(square);
});
std::cout << "Getting cube..." << std::endl;
int cube = future_cube.get();
std::cout << "Cube: " << cube << std::endl;
return 0;
}
代码解释:
std::async(std::launch::async, calculate_square, 5)
: 创建一个task
计算 5 的平方。future_square.share()
: 将future_square
转换为shared_future
。shared_future
允许多个线程访问结果。std::async(std::launch::async, [shared_future_square]() { ... })
: 创建第二个task
,它的回调函数会等待future_square
完成,获取平方的结果,然后计算立方。
方法二:手动组合(适用于 C++11/14/17)
在 C++20 之前,then()
并不存在,我们需要手动组合 task
。 这通常涉及到 lambda 表达式和 get()
方法。
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
int calculate_square(int x) {
std::cout << "Calculating square in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * x;
}
int calculate_cube(int x) {
std::cout << "Calculating cube in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * x * x;
}
int main() {
std::future<int> future_square = std::async(std::launch::async, calculate_square, 5);
std::future<int> future_cube = std::async(std::launch::async, [&future_square]() {
int square = future_square.get(); // Get the result of the square calculation
return calculate_cube(square);
});
std::cout << "Getting cube..." << std::endl;
int cube = future_cube.get();
std::cout << "Cube: " << cube << std::endl;
return 0;
}
代码解释:
std::async(std::launch::async, calculate_square, 5)
: 创建一个task
计算 5 的平方。std::async(std::launch::async, [&future_square]() { ... })
: 创建第二个task
,它的回调函数会等待future_square
完成,获取平方的结果,然后计算立方。 注意这里使用了 lambda 表达式,并且通过引用捕获了future_square
。
shared_future
:多个线程共享结果
有时候,我们需要让多个线程共享一个task
的结果。这时候,就可以用shared_future
。shared_future
允许多个线程同时访问同一个task
的结果。
#include <iostream>
#include <future>
#include <thread>
#include <vector>
int calculate_value() {
std::cout << "Calculating value in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
return 100;
}
void process_value(std::shared_future<int> shared_future, int thread_id) {
std::cout << "Thread " << thread_id << " waiting for result..." << std::endl;
int value = shared_future.get();
std::cout << "Thread " << thread_id << " received value: " << value << std::endl;
}
int main() {
std::future<int> future_value = std::async(std::launch::async, calculate_value);
std::shared_future<int> shared_future_value = future_value.share();
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
threads.emplace_back(process_value, shared_future_value, i);
}
for (auto& thread : threads) {
thread.join();
}
return 0;
}
代码解释:
std::shared_future<int> shared_future_value = future_value.share()
: 这行代码把future_value
转换成shared_future
。process_value(std::shared_future<int> shared_future, int thread_id)
: 这个函数会等待shared_future
的结果,并打印出来。- 多个线程可以同时调用
shared_future.get()
,而不会出现竞争条件。
task
的注意事项和最佳实践
- 避免死锁: 在使用
get()
方法时,要特别小心死锁。例如,在一个task
的回调函数中调用同一个task
的get()
方法,可能会导致死锁。 - 异常处理: 要确保正确处理
task
中可能抛出的异常。否则,可能会导致程序崩溃。 - 避免长时间阻塞: 尽量避免在主线程中长时间阻塞。可以使用
wait_for()
方法设置超时时间。 - 选择合适的
launch
策略: 根据实际情况选择合适的launch
策略。std::launch::async
适用于需要并行执行的任务,std::launch::deferred
适用于只需要在需要结果的时候才执行的任务。 - 使用
shared_future
共享结果: 当多个线程需要访问同一个task
的结果时,使用shared_future
。
task
与其他异步编程技术的比较
特性 | task (std::future) |
std::thread |
Boost.Asio |
---|---|---|---|
抽象级别 | 高 | 低 | 中 |
异常处理 | 内置,自动传播 | 需要手动处理 | 需要手动处理 |
结果获取 | get() |
共享内存/消息队列 | 回调函数 |
组合性 | C++20 then() ,手动组合 |
困难 | 复杂 |
适用场景 | 简单异步任务,需要结果 | 细粒度线程控制 | 网络编程 |
总结:
task
适合于需要获取结果的简单异步任务。std::thread
适合于需要细粒度线程控制的场景。- Boost.Asio 适合于网络编程。
总结
task
是C++异步编程的利器。它可以统一封装异步操作,简化异常处理,方便组合异步操作。掌握task
的用法,可以让你写出更简单、更安全、更可控的异步代码。 希望今天的课程对大家有所帮助! 感谢各位的观看,下次再见!