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

2023-12-18 19:21:21 浏览数 (2)

一,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新特性解析与应用》

0 人点赞