前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C/C++开发基础——std::future与async异步编程

C/C++开发基础——std::future与async异步编程

作者头像
Coder-Z
发布2023-12-18 19:21:21
3190
发布2023-12-18 19:21:21
举报

一,std::future与std::promise

std::future是一个类模板,存放了线程入口函数的返回结果,调用std::future对象的get()函数可以拿到返回结果。

std::promise也是一个类模板,可以基于std::promise实现线程之间的数据传输。

构造一个std::promise对象时,可以和std::future对象相互关联。

1.std::thread与std::future的对比

std::thread启动的线程不容易获取线程的计算结果。

std::thread启动的线程如果抛出了异常,且异常没有被线程本身处理的时候,这个线程会导致整个应用程序发生终止。

std::future可以很方便地获取线程的执行结果,如果线程抛出了异常,std::future可以将异常转移到另一个线程中,让另一个线程来处理异常。

代码样例: std::future调用get()传递异常给另一个线程

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>

//线程函数
int CalculateSum() {
    throw std::runtime_error("Exception throw from CalculateSum.");
}

int main()
{
    auto future_obj = std::async(std::launch::async, CalculateSum);
    try {
        int res = future_obj.get();
        std::cout << res << std::endl;
    }
    catch (const std::exception& ex) {
        std::cout << "Caught exception: " << ex.what() << std::endl;
    }
}

运行结果:

代码语言:javascript
复制
Caught exception: Exception throw from CalculateSum.

2.std::promise和std::future的区别

同一个线程或者另一个线程将线程函数的计算结果放入到std::promise中,而std::future可以获取std::promise中存储的线程计算结果。因此,std::promise是线程计算结果的输入端,std::future是线程计算结果的输出端。

3.std::future的常用成员函数

1.get:阻塞式地获得线程返回结果。

2.wait:等待结果变得可用,此时不会获取线程的执行结果。

3.wait_for:非阻塞式地获得线程返回结果。

std::future通过get()获取线程执行结果,如果线程尚未执行结束,对get()的调用将阻塞,直到该结果可以被获取。

std::future可以先通过调用wait_for()方法,查询结果是否可用来避免阻塞。

std::future只能调用一次get()成员函数来获取结果,继续调用多次会引发异常。

4.std::promise的常用成员函数

1.set_value:指定线程返回结果。

2.get_future:返回与线程关联的future。

3.set_exception:指定线程返回的异常。

std::promise对象只能被移动,不能被复制。

代码样例:子线程和主线程之间同步字符串数据

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <future>

void modifyMessage(std::promise<std::string>&& proms, std::string msg)
{
    std::string metaMsg = msg + " has been modified";
    proms.set_value(metaMsg);
}

int main()
{
    std::string msg_str = "My Message";

    //创建promise对象
    std::promise<std::string> proms;

    //创建一个关联的future对象
    std::future<std::string> future_obj = proms.get_future();

    //给线程传递promise对象
    std::thread t(modifyMessage, std::move(proms), msg_str);

    //打印原始msg_str
    std::cout << "Original message from main(): " << msg_str << std::endl;

    //打印被子线程修改的msg_str
    std::string messageFromThread = future_obj.get();
    std::cout << "Modified message from thread(): " << messageFromThread << std::endl;

    t.join();
    return 0;
}

运行结果:

代码语言:javascript
复制
Original message from main(): My Message
Modified message from thread(): My Message has been modified

二,std::shared_future使用说明

std::shared_future是一个类模板,用法和std::future相似。

std::shared_future可以让多个线程共享同一个状态,从而实现多线程通信。

std::shared_future的常用成员函数

1.get:阻塞式地获得线程返回结果。

2.wait:等待结果变得可用,此时不会获取线程的执行结果。

3.wait_for:非阻塞式地获得线程返回结果。

std::shared_future的成员函数的用法和std::future基本一致,主要区别在于,std::shared_future的get()函数是用来复制数据的,而不是移动数据,这样设计可以让多个线程都可以通过get()获取结果。因此,std::future对象只能执行一次get()函数,而std::shared_future对象可以执行多次get()函数。

三,std::async使用说明

std::async是一个函数模板,通常用来启动一个异步任务,std::async执行结束会返回一个std::future对象。

1.std::async的传参方式

std::async传参的方式和std::thread十分类似。

可以使用std::launch给std::async传参,std::launch可以控制是否给std::async创建新线程。

当不指定std::launch参数时,std::async根据系统资源,自行选择一种执行方法。

结合传参方式,可以总结出,std::async执行线程函数的方法有两种:

1.创建一个新的线程,异步执行线程函数。

2.不创建新线程,在主调线程上同步执行线程函数。

通过传参std::launch来让std::async选择指定方式执行线程函数的方法有三种:

std::launch::async:创建新线程,异步执行线程函数。

std::launch::deferred:返回的std::future对象显式调用get()时,在主调线程上同步执行线程函数。

std::launch::async | std::launch::deferred:代码运行时根据系统资源,选择默认的执行方法。

代码样例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>

//线程函数
int CalculateSum(int a, int b) {
    std::cout << "In other thread." << std::endl;
    return a + b;
}

int main()
{
    auto future_obj = std::async(CalculateSum, 12, 16);
    std::cout << "In Main thread." << std::endl;
    int res = future_obj.get();
    std::cout << res << std::endl;
}

运行结果:

代码语言:javascript
复制
In Main thread.In other thread.

28

2.std::async和std::thread的区别

std::thread直接创建线程,而std::async异步创建一个任务,这个任务可以创建新线程,也可以不创建线程,可以避免占用系统资源。

由于std::async不一定会创建新线程,因此,当系统内存资源不足的时候,继续运行std::thread会使系统崩溃,而std::async此时不会创建新线程,避免了系统崩溃。

std::thread创建的线程不容易获取线程函数的返回值,std::async执行完返回一个std::future对象,可以很容易获取线程函数的返回值。

四,std::packaged_task包装器

std::packaged_task包装器可以生成一个可调用的对象,并且允许异步获取该对象的执行结果。

std::packaged_task是一个类模板,常用的成员函数是get_future(),用于返回一个关联的std::future对象,使用std::packaged_task时可以不需要显式地使用std::promise。

代码样例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>

//线程函数
int CalculateSum(int a, int b) {
    return a + b;
}

int main()
{
    std::packaged_task<int(int, int)> task(CalculateSum);
    auto future_obj = task.get_future();
    std::thread thread_01{ std::move(task), 12, 16 };
    int res = future_obj.get();
    std::cout << res << std::endl;
    thread_01.join();
}

运行结果:

代码语言:javascript
复制
28

使用std::packaged_task可以将各种可调用对象包装起来,方便作为线程的入口函数来调用。

代码样例:

Demo1:packaged_task包装普通函数,然后被调用

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>

using namespace std;

int mythread(int n)
{
    cout << "Thread Input: " << n << endl;
    cout << "mythread start " << "threadId=" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(2000);
    std::this_thread::sleep_for(dura);
    cout << "mythread end " << "threadId=" << std::this_thread::get_id() << endl;
    return 5;
}

int main()
{
    cout << "Main thread start " << "threadId=" << std::this_thread::get_id() << endl;
    std::packaged_task <int(int)> mypt(mythread);
    std::thread t1(std::ref(mypt), 1);
    t1.join();
    std::future<int> res = mypt.get_future();
    cout << "Thread Output: " << res.get() << endl;
    cout << "Main thread end " << "threadId=" << std::this_thread::get_id() << endl;
    return 0;
}

运行结果:

代码语言:javascript
复制
Main thread start threadId=4340
Thread Input: 1
mythread start threadId=12168
mythread end threadId=12168
Thread Output: 5
Main thread end threadId=4340

Demo2:packaged_task包装lambda表达式,然后被调用

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>

using namespace std;

int main()
{
    cout << "Main thread start " << "threadId=" << std::this_thread::get_id() << endl;
    std::packaged_task <int(int)> mypt([](int n){
    cout << "Thread Input: " << n << endl;
    cout << "mythread start " << "threadId=" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(2000);
    std::this_thread::sleep_for(dura);
    cout << "mythread end " << "threadId=" << std::this_thread::get_id() << endl;
    return 10;
    });

    std::thread t1(std::ref(mypt), 1);
    t1.join();
    std::future<int> res = mypt.get_future();
    cout << "Thread Output: " << res.get() << endl;

    cout << "Main thread end " << "threadId=" << std::this_thread::get_id() << endl;
    return 0;
}

运行结果:

代码语言:javascript
复制
Main thread start threadId=19596
Thread Input: 1
mythread start threadId=21124
mythread end threadId=21124
Thread Output: 10
Main thread end threadId=19596

Demo3:packaged_task包装成对象,然后被调用

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>

using namespace std;

int main()
{
    cout << "Main thread start " << "threadId=" << std::this_thread::get_id() << endl;
    std::packaged_task <int(int)> mypt([](int n){
    cout << "Thread Input: " << n << endl;
    cout << "mythread start " << "threadId=" << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(2000);
    std::this_thread::sleep_for(dura);
    cout << "mythread end " << "threadId=" << std::this_thread::get_id() << endl;
    return 15;
    });

    mypt(10); //可调用对象
    std::future<int> res = mypt.get_future();
    cout << "Thread Output: " << res.get() << endl;

    cout << "Main thread end " << "threadId=" << std::this_thread::get_id() << endl;
    return 0;
}

运行结果:

代码语言:javascript
复制
Main thread start threadId=20868
Thread Input: 10
mythread start threadId=20868
mythread end threadId=20868
Thread Output: 15
Main thread end threadId=20868

五,参考阅读

C++新经典》

《C++高级编程》

《深入理解C++11:C++11新特性解析与应用》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一,std::future与std::promise
  • 二,std::shared_future使用说明
  • 三,std::async使用说明
  • 四,std::packaged_task包装器
  • 五,参考阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档