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对象不可以按值传递,只可以按引用传递或者使用移动语义,这是可以理解的。
promisefuture对象只可以有一个,即一对一的关系,当设置了多个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对象复制出来。
无论什么时候,都不要对一个promisepackaged_task对象多次调用get_future方法,难办的点在于,这个错误并不会在编译时暴露出来,但会导致程序在运行阶段崩溃。

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718169

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示