Featured image of post C++异步编程工具 (一)

C++异步编程工具 (一)

介绍标准库中future、async、packaged_task、promise等工具。

引言

std::asyncstd::packaged_taskstd::promise 是C++11引入的三个用于异步编程的工具,它们都与 std::future 配合使用,用于在多线程环境中执行任务并获取结果。本篇文中的工具都在<future>头文件中声明。

std::future

std::future 的核心功能

  • 获取异步操作的结果
    • 通过 get() 方法获取异步操作的结果。
    • 如果结果尚未准备好,get() 会阻塞当前线程,直到结果可用。
  • 检查异步操作的状态
    • 通过 valid() 方法检查 std::future 是否关联了一个有效的共享状态。
    • 通过 wait()wait_for()wait_until() 方法等待异步操作完成。
  • 移动语义
    • std::future 只能移动(Move),不能复制。

std::future 的主要方法

方法 描述
get() 获取异步操作的结果。如果结果未准备好,会阻塞当前线程直到结果可用。
valid() 检查 std::future 是否关联了一个有效的共享状态。
wait() 阻塞当前线程,直到异步操作完成。
wait_for() 阻塞当前线程一段时间,等待异步操作完成。
wait_until() 阻塞当前线程直到某个时间点,等待异步操作完成。
share() std::future 转换为 std::shared_future,允许多个线程共享结果。

get和wait方法

wait方法没有返回值,只是等待异步操作完成

get方法等待异步操作完成并会返回具体future<T>中T类型的值

调用wait方法后,还可以调用get方法;返过来则不行

  • 具体使用看后面小节与其他工具相互配合

std::async

  • async是一个模板函数,下面是这个函数的使用方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int func1() {
    std::cout << " (hello world in func1) " << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    return 1024;
}

void func2() {
    std::cout << " (hello world in func2) " << std::endl;
}

int main() {
    //第一个参数的两种形式deferred   async(可以省略)
    std::future<int> f1 = std::async(std::launch::deferred, func1);
    //如果第一个参数是deferrd,则func1不会立即执行,等到后面调用get()或wait()方法时才会执行。
    //如果第一个参数是async,创建一个新线程立刻执行func1,并返回结果。(async可以省略)
    //如果第一个参数是 deferred | async ,那么就会由async的实现自行决定选择运行方式
    func2();

    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << " Result: " << f1.get() << std::endl;       //等待func1执行完毕,并获取结果
}

std::package_task

std::packaged_task 的核心功能

  • 包装可调用对象
    • std::packaged_task 可以包装一个可调用对象(函数、lambda表达式、函数对象等)。
  • 关联 std::future
    • 通过 get_future() 方法,可以获取一个与任务结果关联的 std::future 对象。
  • 执行任务
    • 调用 operator() 或将其传递给线程执行时,任务会被执行,并将返回值或异常自动设置到 std::future 中。

std::packaged_task 的主要方法

方法 描述
operator() 执行包装的可调用对象,并将返回值或异常与 std::future 关联。
get_future() 获取与 std::packaged_task 关联的 std::future 对象。
reset() 重置 std::packaged_task,使其可以重新包装一个新的可调用对象。

具体使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//函数模板参数指定函数的参数和返回值
//传入的函数不需要严格匹配,但是函数参数应当可以进行隐式转换
//该对象含有operator(),是调用对象,可以传给std::function
//该对象可以移动,但是不能拷贝
int print(int d) {
    cout << d << endl;
    return d;
}

int main() {
    std::packaged_task<int(double)> task(print);	//传入函数参数可以发生隐式类型转换即可
    std::future<int> fut = task.get_future();

    //在main函数执行
    task(3.14159265359);
    cout << fut.get() << endl;	//执行完task才会得到结果
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
int print(int d) {
    this_thread::sleep_for(chrono::seconds(5));
    cout << d << endl;
    return d;
}

void func(packaged_task<int(double)>&& task, const double d) {
    task(d);
}

int main() {
    packaged_task<int(double)> task(print);
    future<int> fut = task.get_future();

    //在新线程上面去执行task
    thread t1(func,std::move(task),3.1415926);
    cout << fut.get() << endl;		//等待t1线程的task执行结束才会获得值
    t1.join
}

std::async和std::packaged_task<>使用对比

std::async

  • 将任务(函数)交给它,由它决定立刻执行还是延时执行,并直接返回future对象用来获取返回值。
  • std::launch::async立刻执行函数,会创建新线程
  • std::launch::deferred不会立刻执行,future对象调用wait或get时候才执行,不会创建新线程

std::packaged_task

  • 直接绑定任务,通过get_future成员方法得到future对象,通过oprator()进行任务的调用。
  • 手动控制调用的时机

对比下面三个程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int print(int d) {
  this_thread::sleep_for(chrono::seconds(5));
  cout << d << endl;
  return d;
}

void func(future<int>& fut) {
  this_thread::sleep_for(chrono::seconds(10));
  fut.wait();
}

int main() {
  future<int> fut =  async(launch::deferred, print, 10);
  thread t1(func,std::ref(fut));
  cout << fut.get() << endl;		//立刻执行get,但是t1线程之后会执行wait导致报错。
  t1.join();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int print(int d) {
  this_thread::sleep_for(chrono::seconds(5));
  cout << d << endl;
  return d;
}

void func(future<int>& fut) {
  this_thread::sleep_for(chrono::seconds(10));
  fut.wait();
}

int main() {
  future<int> fut =  async(launch::deferred, print, 10);
  thread t1(func,std::ref(fut));

  auto state = fut.wait_for(chrono::seconds(0));
  while (state != future_status::ready) {		//异步任务还没有结束
      state = fut.wait_for(chrono::milliseconds(200));	//继续等待
  }

  cout << fut.get() << endl;	//调用get时候,异步任务以及结束,这里只是获得结果
  t1.join();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int print(int d) {
  this_thread::sleep_for(chrono::seconds(5));
  cout << d << endl;
  return d;
}

void func(packaged_task<int(int)>&& task, const int d) {
  this_thread::sleep_for(chrono::seconds(10));
  task(d);
}

int main() {
  packaged_task<int(int)> task(print);
  future<int> fut = task.get_future();
  thread t(func,std::move(task),10);
  cout << fut.get() << endl;
  t.join();
}

std::promise

std::promise 的核心功能

  • 用途std::promise 用于手动设置一个值或异常,并将其与 std::future 关联。
  • 特点
    • 需要显式调用 set_value()set_exception() 来设置值或异常。
    • 适用于需要手动控制结果设置的场景。
    • 通常用于将结果从一个线程传递到另一个线程。
    • std::promise 不能复制,但它支持移动语义(Move Semantics)

std::promise 的主要方法

方法 描述
set_value() 设置值,并将 std::future 标记为就绪。
set_exception() 设置异常,并将 std::future 标记为就绪。
get_future() 获取与 std::promise 关联的 std::future 对象。

std::promise 的使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <thread>
#include <future>

void task(std::promise<int> prom) {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟耗时操作
    prom.set_value(42);  // 设置值
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();  // 获取与 promise 关联的 future

    std::thread t(task, std::move(prom));  // 启动线程,传递 promise

    std::cout << "Waiting for the result..." << std::endl;
    int result = fut.get();  // 阻塞,直到 promise 设置值
    std::cout << "Result: " << result << std::endl;

    t.join();  // 等待线程结束
    return 0;
}

future保存异常

  • 一下几种情况可以将异常保存到future中,当future.get()的时候会重新抛出异常
    • async执行操作的时候发生异常,会将异常保存到future中。
    • packaged_task执行任务函数的时候抛出异常,会将异常保存到future中。
    • promise对象调用set_exception()设置异常。
    • 当async和packaged_task对象在future未就绪的时候被销毁,他们的析构函数就会将std::future_error存储为异步任务的状态,它的值(std::future_error::code()方法获得)是std::future_errc::broken_promise(枚举类型)。