0
点赞
收藏
分享

微信扫一扫

C++ 异步编程

Mezereon 2023-06-05 阅读 77

1. 异步编程含义及作用

      相对于同步编程方式时,由于每个线程同时只能发起一个请求并同步等待返回,所以为了提高系统性能,此时我们就需要引入更多的线程来实现并行化处理。但是 多线程下对共享资源进行访问时,不可避免会引入资源争用和并发问题;另外,操作系统层面对线程的个数是有限制的,不可能通过无限制的增加线程来提供系统性能;而且,使用同步阻塞的编程方式还会浪费资源,比如发起网络IO请求时,调用线程就会处于同步阻塞等待响应结果的状态,而这时候调用线程明明可以去做其他事情,等网络IO响应结构返回后再对结构进行处理。而异步编程是可以让程序并行运行的一种手段,其可以让程序中的一个工作单元与主应用 程序线程分开独立运行,并且在工作单元运行结束后,会通知主应用程序线程它的运行结果 或者失败原因。使用异步编程可以提高应用程序的性能和响应能力.

2. C++中的异步编程知识

std::future对象:

    std::future是一个类模板,提供了一个访问异步操作的结果的机制。我们可以通过future_status去查询future的三种状态,分别是deferred(还未执行),ready(已经完成),timeout(执行超时),所以我们可以通过这个去查询异步操作的状态。future提供了一些函数比如get(),wait(),wait_for(),一般用get()来获取future所得到的结果,如果异步操作还没有结束,那么会在此等待异步操作的结束,并获取返回的结果。wait()只是在此等待异步操作的结束,并不能获得返回结果。wait_for()超时等待返回结果。

     此外,std::future的get()方法只能调用一次;std::future不支持拷贝,支持移动构造。c++提供的另一个类std::shared_future支持拷贝,允许共享,可调用多次get()函数给多个线程获取返回值。可以通过下面三个方式来获得std::future。

  • std::promise的get_future函数
  • std::packaged_task的get_future函数
  • std::async 函数

std::packaged_task:

       std::packaged_task是一个类模板,将一个可调用对象(仿函数,普通函数,lambda表达式,成员函数等)封装起来,然后可以将其的返回值传给future。std::packaged_task<函数返回类型(参数类型)> 变量名(函数名)。std::package_task类似于std::functional,需要显示的调用,特殊的是,它自动会把返回值可以传递给std::future。

因为 std::packaged_task 对象是一个可调用对象, 可以:

  • 封装在 std::function 对象中;
  • 作为线程函数传递到 std::thread 对象中;
  • 作为可调用对象传递另一个函数中;
  • 可以直接进行调用 ;

std::packaged_task()的简单用法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#include <iostream>

#include <future>

#include <thread>

int fun(int x) {

    x++;

    x *= 10;

    std::cout << std::this_thread::get_id() << std::endl;

    std::this_thread::sleep_for(std::chrono::seconds(5));

    return x;

}

int main()

{

    std::packaged_task<int(int)> pt(fun);         // 将函数打包起来

    std::future<int> fu = pt.get_future();        // 并将结果返回给future

    std::thread t(std::ref(pt), 1);

    std::cout << fu.get() << std::endl;

    std::cout << std::this_thread::get_id() << std::endl;

    t.join();

    return 0;

}

std::promise

     std::promise是一个类模板,它的作用是在不同的线程中实现数据的同步与future结合使用,也间接实现了future在不同线程间的同步。promise还有一个函数是set_value_at_thread_exit(), 作用是当在这个线程执行结束的时候才会将future的状态设置为ready,而set_value()则直接将future的状态设置为ready。需要注意的是在使用的过程中不能多次set_value(),也不能多次get_future()和多次get(),因为一个promise对象只能和一个future对象相关联,否则就会抛出异常。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#include <iostream>

#include <future>

#include <thread>

int fun(int x, std::promise<int>& p) {

    x++;

    x *= 10;

    p.set_value(x);

    std::cout << std::this_thread::get_id() << std::endl;

    return x;

}

int main()

{

    std::promise<int> p;

    std::future<int> fu = p.get_future();        // 并将结果返回给future

    std::thread t(fun, 1, std::ref(p));

    std::cout << fu.get() << std::endl;          // 当promise还没有值的时候在此等待

    std::cout << std::this_thread::get_id() << std::endl;

    t.join();

    return 0;

}

std::async

       其实这个函数是对上面的对象的一个整合,async先将可调用对象封装起来,然后将其运行结果返回到promise中,这个过程就是一个面向future的一个过程,最终通过future.get()来得到结果。它的实现方法有两种,一种是std::launch::async,这个是直接创建线程,另一种是std::launch::deferred,这个是延迟创建线程(当遇到future.get或者future.wait的时候才会创建线程),这两个参数是std::async的第一个参数,如果没有使用这个两个参数,也就是第一个参数为空的话,那么第一个参数默认为std::launch::async 或者 std::launch::deferred,这个就不可控了,由操作系统根据当时的运行环境来确定是当前创建线程还是延迟创建线程。那么std::async的第二个参数就是可调用对象的名称,第三个参数就是可调用对象的参数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include <iostream>

#include <future>

#include <thread>

int fun(int x) {

    x++;

    x *= 10;

    std::cout << std::this_thread::get_id() << std::endl;

    return x;

}

int main()

{

    // std::launch::deferred 当执行到fu.get才开始创建线程

    std::future<int> fu = std::async(std::launch::deferred, fun, 1);

    std::cout << fu.get() << std::endl;

    std::cout << std::this_thread::get_id() << std::endl;

    return 0;

}

异常捕获:

std::async和std:packeged_task处理异常:

     它们把异常传递给future对象,通过future.get()可以获得async中的异常,外部套一个try/catch。至于是原始的异常对象, 还是一个拷贝,不同的编译器和库将会在这方面做出不同的选择。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void foo()

{

  std::cout << "foo()" << std::endl;

  throw std::runtime_error("Error");

}​

int main()

{

  try

  {

    auto f = std::async(std::launch::async, foo);

    f.get();

  }

  catch (const std::exception& ex)

  {

    std::cerr << ex.what() << std::endl;

  }

}

std::promise处理异常:

     std::promise处理异常与上面两者不同,当它存入的是一个异常而非一个数值时, 就需要调用set_exception()成员函数, 而非set_value()。这样future才能捕获异常。

1

2

3

4

5

6

try{

    some_promise.set_value(calculate_value());

}

catch(...){

    some_promise.set_exception(std::current_exception());

}

举报

相关推荐

0 条评论