future、promise、packaged-task
future#
标准库中提供的future
用于接收一个异步执行结果,常和async
配合使用,async
执行一个异步操作,并将结果封装成一个future
对象,外部通过future
提供的get方法来获取异步执行的结果。
int main() {
cout << "call async\n";
auto startpoint = chrono::steady_clock::now();
future<chrono::steady_clock::time_point> f = async(launch::async, []() {
cout << "async execute...\n";
this_thread::sleep_for(seconds(3));
return chrono::steady_clock::now();
});
cout << "do something else...\n";
cout << "wait...\n";
this_thread::sleep_for(chrono::seconds(1));
auto endpoint = f.get();
auto consumed = chrono::duration_cast<chrono::milliseconds>(endpoint - startpoint).count();
cout << "get future, elapsed: " << consumed << "ms.\n";
return 0;
}
比如这样一段代码,执行结果:
call async
do something else...
wait...
async execute...
get future, elapsed: 3002ms.
可以看到,main函数中的程序和async包装的lambda表达式不是同步执行的,总耗时几乎和async的耗时相同,说明main函数中的休眠和aysnc中的休眠是同时异步在执行的。
async
中提供了std::launch
可以设置异步执行的策略,比如上述代码中指定了std::launch::async
表示这个异步操作立即执行,也可以选择std::launch::deferred
来允许异步操作可以在调用的时候再执行,像这样:
int main() {
cout << "call async\n";
auto startpoint = chrono::steady_clock::now();
future<chrono::steady_clock::time_point> f = std::async(launch::deferred, []() {
cout << "async execute...\n";
this_thread::sleep_for(chrono::seconds(3));
return chrono::steady_clock::now();
});
cout << "do something else...\n";
cout << "wait...\n";
this_thread::sleep_for(chrono::seconds(1));
auto endpoint = f.get();
auto consumed = chrono::duration_cast<chrono::milliseconds>(endpoint - startpoint).count();
cout << "get future, elapsed: " << consumed << "ms.\n";
return 0;
}
这样执行的结果如下:
call async
do something else...
wait...
async execute...
get future, elapsed: 4015ms.
可以看到,整体的耗时接近于同步操作的耗时,甚至更久,这是因为这个异步操作在调用get方法的时候才开始执行,再加上线程启停的耗时。
一般这种模式适用于异步任务并不是十分紧急,可以等到需要时再执行的场景,好处就是不会立即占用系统资源。
promise#
future
用于获取异步任务执行的结果,而promise
提供了异步任务中设置共享变量的方法。
int main() {
auto task = [](promise<int>& promise) {
cout << "task execute...\n";
this_thread::sleep_for(chrono::seconds(3));
promise.set_value(42);
};
promise<int> p;
auto f = p.get_future();
auto startpoint = chrono::steady_clock::now();
task(p);
cout << "promise value: " << f.get() << endl;
auto endpoint = chrono::steady_clock::now();
cout << "elapsed: " << chrono::duration_cast<chrono::seconds>(endpoint - startpoint).count() << "s\n";
return 0;
}
执行结果:
task execute...
promise value: 42
elapsed: 3s
promise
会可以使用get_future方法来获得一个future
对象,用于值的同步。当使用这个future
对象来获取promise
的值时,会阻塞到promise
调用了promise_value方法为止。
promise
对象不可以按值传递,只可以按引用传递或者使用移动语义,这是可以理解的。
promise
的future
对象只可以有一个,即一对一的关系,当设置了多个future
对象时会在编译阶段报错。
另外,promise
还提供了set_exception方法用于设置异常。
packaged_task#
packaged_task
是标准库提供的一个异步任务的封装,同上述promise
一样,也是通过一个future
对象来接收执行结果。
int main() {
packaged_task<string()> task([]() {
auto utc = chrono::system_clock::to_time_t(chrono::system_clock::now());
this_thread::sleep_for(chrono::seconds(3));
return ctime(&utc);
});
auto f = task.get_future();
jthread t(move(task));
auto time = chrono::system_clock::to_time_t(chrono::system_clock::now());
cout << "main time: \n" << ctime(&time);
cout << "task result:\n" << f.get();
}
执行结果:
main time:
Thu Aug 8 22:09:53 2024
task result:
Thu Aug 8 22:09:53 2024
可以看到,任务可以放到线程中去执行,然后在需要的时候再通过future
的get方法去获取执行结果。
这才是
packaged_task
真正的用武之地,它封装了一个带有返回值的任务函数,将其放到了线程中去执行,必要的时候再通过绑定的future
对象来获取执行结果。
需要注意的是,当传递给线程对象执行时,务必使用移动语义或显式引用,因为thread
对象对入参默认执行的是拷贝操作,而对packaged_task
执行拷贝操作等于重新生成了一个新的packaged_task
,原来绑定的future
对象也就失效了,并且也不会通过编译。
示例代码比较简单,实际上完全可以将其放到一个任务队列中去,这就可以很轻易地实现一个生产者消费者场景。
shared_future#
上述future
对象只可以实现一对一的绑定,这也是出于数据安全的考虑,但有时候任务结果确实需要被多个对象来共享,这就得使用shared_future
了。
int main() {
packaged_task<int(int, int)> task([](int a, int b) -> int {
cout << "calculate...\n";
this_thread::sleep_for(chrono::seconds(3));
return a + b;
});
shared_future<int> sf = task.get_future();
thread t1([](shared_future<int> f) {
int result = f.get();
cout << "thread 1 get future: " << result << endl;
}, sf);
thread t2([](shared_future<int> f) {
int result = f.get();
cout << "thread 2 get future: " << result << endl;
}, sf);
thread t(move(task), 1, 1);
thread t3([](shared_future<int> f) {
int result = f.get();
cout << "thread 3 get future: " << result << endl;
}, sf);
t.join();
t1.join();
t2.join();
t3.join();
return 0;
}
执行结果如下,因为多线程的原因,输出顺序被打乱了:
calculate...
thread 1 get future: thread 3 get future: 22
thread 2 get future: 2
切记,就算是使用了
shared_future
,get_future方法也只允许调用一次,其他共享的shared_future
对象可以通过绑定的shared_future
对象复制出来。
无论什么时候,都不要对一个promise
或packaged_task
对象多次调用get_future方法,难办的点在于,这个错误并不会在编译时暴露出来,但会导致程序在运行阶段崩溃。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话