C++11并发编程常用库之<future>

#include <future>:用于管理和处理异步任务

包含:

Providers类(异步任务提供者):

std::promise   用于设置异步任务的结果

std::packaged_task  将函数包装为异步任务

Futures类:

std::future  获取异步任务的结果

std::shared_future  允许多个对象共享异步任务的结果

Providers函数:

std::async()

其他类型:

std::future_error

std::future_errc

std::future_status

std::launch

std::promise和std::future的关系:

std::promise对象用于保存某种类型T的值。我们可以通过std::promise设置一个值,并让其他线程读取这个值

std::future对象用于从std::promise读取这个值。通常,std::future对象会在另一个线程中读取std::promise设置的值

共享状态:

std::promise和std::future通过一个叫共享状态的机制进行通信。当你创建一个std::promise对象时,它会与一个共享状态相关联。可以通过std::promise的get_futre()方法来获取与这个std::promise对象关联的std::future对象。这两个对象(std::promise和std::future)通过共享状态相连接

设置和读取值:

作为异步任务的提供者,std::promise可以在任何时刻设置共享状态中的值。这个值会被传递给所有关联的std::future对象

std::future对象负责从共享状态中读取这个值。它可以异步的获取值,也就是说它可以在后台线程中等待值的设置,或者在必要时阻塞当前线程,直到值被设置为止。

一、std::promise

std::promise构造函数

1.默认构造:会初始化一个空的共享状态

std::promise<int> p;

std::future<int> f=p.get_future(); 获取与promise关联的future

2.带自定义内存分配器的构造函数

std::allocator<int> alloc;

std::promise<int> p(std::allocator_arg,alloc)

std::future<int> f=p.get_future()

3.移动构造函数

std::promise<int> p1;

std::future<int> f=p1.get_future();

std::promise<int> p2(std::move(p1));

std::future<int> f=p2.get_future();

4.不允许拷贝构造函数 std::promise<int> p2(p1);

std::promise::get_future介绍

std::future<int> f=p.get_future();
  1. 调用get_future会返回一个std::future对象,这个std::future和std::promise共享一个状态,这个状态最终会包含一个结果值或者一个异常。
  2. 每个std::promise对象只能提供一个std::future。一旦你调用了get_future,这个std::promise对象就不可以再提供给其他的std::future
  3. 我们通常会在某个时刻通过std::promise对象设置一个结果值或者抛出一个异常。Std::future对象将用来获取这个值或者捕获这个异常。
  4. 如果我们没有设置任何值或异常,即std::promise对象在其生命周期内没有完成,那么在std::promise对象析构时,它会自动设置一个std::future_error异常(具体是std::future_errc::broken_promise),表示这个std::promise对象的状态是破坏的。

std::promise::set_value介绍

设置共享状态的值,此时promise的共享状态标志变为ready

std::promise<int> p1;

p1.set_value(10);

std::promise::set_excption介绍

为promise对象设置异常,此后promise的共享状态标志变为ready。

try {
     throw std::runtime_error("Something went wrong!");  // 主动抛出异常
} catch (...) {
     p.set_exception(std::current_exception());  // 捕获当前异常,传给promise
}

std::promise::set_value_at_thread_exit介绍

用于设置共享状态的值

  1. 设置值但延迟标记完成,虽然设置了值但共享状态标志非ready,只有当该线程结束时才将共享状态的标志置为ready
  2. 如果有一个std::future对象与这个std::promise关联,当在主线程中你调用future::get时,当前主线程线程会被阻塞。这个阻塞会持续到调用set_value_at_thread_exit函数的线程执行完成并自动设置共享状态为完成,一旦该线程结束,主线程中的future::get就会解除阻塞,并返回你通过set_value_at_thread_exit设置的值。
  3. 调用set_value_at_thread_exit后,不能再通过其他方法设置这个std::promise对象的值或异常。如果仍然设置会抛出std::future异常,错误类型是promise_already_satisfied,表示这个promise已经被满足,不能再进行其他修改
void worker(std::promise<int> p) {
    p.set_value_at_thread_exit(42);  // 设置值,但延迟到线程退出时才生效
}

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future();

    std::thread t(worker, std::move(p));

    std::cout << "Waiting for the result...\n";
    std::cout << "Result: " << f.get() << std::endl;  // 会阻塞直到worker线程退出

    t.join();
    return 0;
}

std::promise::swap介绍

交换promise的共享状态。

std::promise<int> p1;
std::promise<int> p2;

p1.set_value(10);  // 给p1赋一个值
// p2还没有设置值

p1.swap(p2);  // 交换p1和p2的共享状态

// p1现在变成了空的(因为p2原来没设置值),p2接管了原p1的状态
std::future<int> f = p2.get_future();
std::cout << "Value from promise after swap: " << f.get() << std::endl;    //输出10

二、future

std::future详解

Std::future可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段。Std::future通常是由某个provider创建,你可以把provider想象成一个异步任务的提供者,provider在某个线程设置共享状态的值,与该共享状态相关联的std::future对象调用get(通常在另外一个线程中)获取该值,如果共享状态的标志不为ready,则调用std::future::get会阻塞当前的调用者,直到provider设置了共享状态的值(此时共享状态变为ready),std::future::get返回异步任务的值或异常。

Std::future对象创建的三种方式

# std::future 对象的三种创建方式简单总结

### 1. 使用 `std::async`
- `std::async` 是一个函数,启动一个异步任务,并**直接返回一个 std::future** 对象。
- **特点**:不需要自己管理 promise 或 task,系统自动处理,适合简单异步调用。

```cpp
std::future<int> fut = std::async(std::launch::async, some_function, args...);
```

---

### 2. 使用 `std::promise::get_future`
- `std::promise` 是一个类,用来**手动设置结果**(比如 set_value、set_exception)。
- 通过 `promise.get_future()` 创建对应的 `std::future`,用于异步地等待结果。
- **特点**:需要自己负责什么时候、用什么值去满足 future,适合手动控制数据来源。

```cpp
std::promise<int> p;
std::future<int> fut = p.get_future();
```

---

### 3. 使用 `std::packaged_task::get_future`
- `std::packaged_task` 是一个类,用来**封装可调用对象**(函数、lambda等)。
- 通过 `task.get_future()` 创建对应的 `std::future`,在任务执行后获取返回结果。
- **特点**:需要先封装任务,手动调度(如放在线程中执行),适合复杂调度控制。

```cpp
std::packaged_task<int(int)> task(some_function);
std::future<int> fut = task.get_future();
```

---

# 简单归纳
- `std::async`:系统帮你管理,直接返回future。
- `std::promise::get_future`:自己生成future,自己设置结果。
- `std::packaged_task::get_future`:封装任务,异步执行后拿future取结果。

注意:一个std::future对象只有在有效的情况下才有用,由std::future默认构造的函数创建的future对象不是有效的(除非当前非有效的future对象被move赋值另一个有效的future对象)。

Std::future构造函数

Std::future对象一般由以上三种方式创建,但也提供了构造函数

默认构造 std::future<int> fut;
Move-赋值操作 fut=std::async(do_some_task) std::async(do_some_task)会返回一个std::future<int>类型对象

Std::future::share()介绍

返回一个std::shared_future对象,调用该函数后,该std::future对象本身已经不和任何共享状态相管关联

Std::future::valid()介绍

检查当前的std::future对象是否有效

Std::future::wait()介绍

等待与当前std::future对象相关联的共享状态的标志变为ready,如果当前的共享状态的标志不是ready,调用该函数会被阻塞当前线程,直到共享状态的标志变为ready。一旦共享状态的标志变为ready,wait()函数返回,当前线程解除阻塞,但是wait()并不读取共享状态的值或异常,只进行等待

Std::future::wait_for()介绍

与std::future::wait()类似,不同的是它可以设置一个时间段rel_time,如果共享状态的标志在该时刻结束之前没有被provider设置为ready,则调用wait_for的线程被阻塞,在等待了rel_time的时间长度后wait_for()返回,返回值如下

  1. future_status::ready:共享状态的标志已经被变为ready,即provider在共享状态上设置了值或者异常
  2. Future_status::timeout:超时,即在规定的时间内共享状态的标志位没有变为ready
  3. Future_status::timeout:共享状态包含一个deferred函数

Std::future::wait_until()介绍

与Std::future::wait_for()类似,区别在于可以设置一个系统绝对时间点

Std::shared_future介绍

提供了一种机制来共享异步操作的结果。与std::future不同的是,std::shared_future允许多个对象共享一个异步操作的结果。

构造函数:

1.默认构造:

        表示一个空的无效的shared_future对象

std::shared_future<int> shared_fut;

2.拷贝构造函数

std::shared_future<int> shared_fut1=std::async(std::launch::deffered,task);
std::shared_future<int> shared_fut2(shared_fut1);

3.移动构造函数

std::shared_future<int> shared_fut1=std::async(std::launch:;deffered,task);
std::shared_future<int> shared_fut2(std::move(shared_fut1);

4.从std::future移动构造

从一个有效的std::future对象创建一个std::shared_future<T>对象,这样可以将std::future的异步操作结果状态转换为std::shared_future,允许多个std::shared_future对象共享这个结果,此时std::shared_future<T>对象将持有std::future对象的共享状态,原来的std::future对象会变成无效,不能再用于获取值或检查状态。

std::future<int> fut1=std::async(std::launch::deffered,task);
std::shared_future<int> shared_fut1=fut1.share();

Std::shared_future成员函数

和std::future类似

1.operator=(): 赋值操作符,与 std::future 的赋值操作不同,std::shared_future 除了支持 move 赋值操作外,还支持普通的赋值操作。

2.get(): 获取与该 std::shared_future 对象相关联的共享状态的值(或者异常)。

3.wait(): 等待与该 std::shared_future 对象相关联的共享状态的标志变为 read y。

4.wait_for(): 等待与该 std::shared_future 对象相关联的共享状态的标志变为 re ady。(等待一段时间,超过该时间段wait_for 返回。)

5.wait_until(): 等待与该 std::shared_future 对象相关联的共享状态的标志变为 r eady。(在某一时刻前等待,超过该时刻 wait_until 返回。)

6.valid(): 有效性检查。

与std::future相关的枚举类介绍

1.std::future_error类型

①broken_promise 0 与该 std::future 共享状态相关联的 std::promise 对象在设置值或者异常之前被销毁。

②future_already_retrieved 1 与该 std::future 对象相关联的共享状态的值已经被当前 Provider 获取了,即调用了 std::future::get 函数。

③promise_already_satisfied 2 std::promise 对象已经对共享状态设置了某一值或者异常。

④no_state 3 无共享状态。

2.std::futire_status类型:主要用在std::future/shared_future中的wait_for和wa it_until两个函数中

①future_status::ready 0 `wait_for`(`或 wait_until`) 因为共享状态的标志变为 re ady 而返回。

②future_status::timeout 1 超时,即 `wait_for`(`或 wait_until`) 因为在指定的时间段(或时刻)内共享状态的标志依然没有变为 `ready` 而返回。

③future_status::deferred 2 共享状态包含了 deferred 函数

3.std::launch类型

①launch::async Asynchronous: 异步任务会在另外一个线程中调用,并通过共享状态返回异步任务的结果(一般是调用 std::future::get() 获取异步任务的结果)。

②launch::deferred Deferred: 异步任务将会在共享状态被访问时调用,相当与按需调用(即延迟(deferred)调用)。

三、async

std::async()函数介绍:异步任务辅助函数

Std::async()是一个用于并发编程的函数模板,提供了一种简单的方法来异步执行任务并获取结果。他在后台线程中执行指定的任务,并返回一个std::future对象,通过该对象可以异步的获取任务的结果或处理可能抛出的异常

不指定启动策略的std::async函数

std::future<int> result=std::async(函数,参数);
result.get();

std::async会根据系统的默认策略决定如何启动异步任务。通常情况下,这种默认策略是根据需要自动选择,也就是说,可能会使用新的线程,也可能会将任务推迟到将来某个时刻

指定启动策略的std::async函数

启动策略可以是std::launch::async、std::launch::deferred、或者他们的‘按位或’组合,(std::launch::async|std::launch::deferred)

①std::launch::async:强制在新的线程中异步执行任务。

②std::launch::defferred:延迟执行任务,直到std::future的get()或者wait()方法被调用。这种策略通常会在调用get()时在当前线程中同步的执行任务。

Std::future<int> result_deferred=std::async(std::launch::deferred,compute,5);

Std::future<int> result_async=std::async(std::launch::async,compute,5);

result_deferred.get();

result_async.get();

四、std::packaged_task

是另一种提供者(provider),用于包装一个可调用的的对象(例如函数、函数对象、lambda表达式等),它可以将这个可调用对象封装起来,准备在未来某个时刻执行。并且允许异步获取该可调用对象产生的结果。

异步获取结果:std::package_task允许你将一个可调用对象封装起来,并将它的执行结果传递给一个std::future对象。Std::future是一个用来获取异步操作结果的机制。可以在一个线程中执行std::package_task,然后再另一个线程中使用std::future对象来等待和获取任务的结果。当std::package_task被执行时,它会将任务的执行结果(即函数的返回值)传递给关联的std::future对象,因此可以在异步任务完成后,通过std::future来获取其结果,而不必在任务完成之前阻塞当前线程(这个意思是指在异步任务进行时,异步任务所在线程不会阻塞,而是会继续执行,该异步任务是在另一个线程中异步执行,即std::future所在线程,并且这个异步任务在执行完成后会把结果传递给另一个线程中关联的std::future)。

示例:

std::packaged_task<int(int, int)> task(计数函数);  // 封装计数函数任务

std::future<int> ret = task.get_future();  // 获取 future 对象,等待任务执行结果

std::thread th(std::move(task), 参数1, 参数2);  // 创建一个新线程,执行计数任务(注意传递参数)

int value = ret.get();  // 等待任务完成,并获取结果

th.join();  // 等待线程执行完毕

std::package_task组成:

  1. 被包装的任务(stored task),任务是一个可调用的对象,如函数指针、成员函数指针或者函数对象
  2. 共享状态:用于保存任务的返回值,可以通过std::future对象来达到异步访问共享状态的效果

std::package_task::get_future介绍

用于获取与共享状态相关联的std::future对象,返回的future对象可以获得由另外一个线程在该package_task对象的共享状态上设置的某个值或某个异常

  1. std::package_task对象是异步provider,他在某一时刻通过调用被包装的任务来设置共享状态的值
  2. Std::future对象是一个异步返回对象,通过它可以获得共享状态的值,在必要的时候需要等待共享状态标志为ready

注意:共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。

std::package_task构造函数:

默认构造函数

       初始化一个空的共享状态,并且该package_task对象无包装任务

std::package_task<int()>  task;

带任务的构造函数

        直接用任务创建std::package_task对象。可以将一个函数或者lambda或者函数对象作为参数

        传递给构造函数

std::package_task<int()> task(函数);

带自定义分配器的构造函数

std::package_task<int()> task(std::allocator_arg,函数);

移动构造函数

std::package_task<int()> task1(函数);
std::package_task<int()> task2(std::move(task1));

std::packaged_task::valid介绍

检查当前package_task是否和一个有效的共享状态相关联,对于由默认构造函数生成的,返回值是false

std::packaged_task::operator()介绍

是一个重载的调用操作符,用于执行std::packaged_task对象所包装的任务。这个调用操作符允许你在运行时将参数传递给封装的任务,并执行它

1.调用operator()的基本行为

(1)调用后他会执行包装的任务。如果任务成功执行并返回一个值,那么这个值会被存储在std::packaged_task的共享状态中(通常是一个std::promise对象的std::shared_ptr)。这意味着其他线程可以通过与std::packaged_task相关联的std::future对象来获取这个值

(2)如果任务在执行过程中抛出了异常,这个异常会被捕获并保存在std::packaged_task的共享状态中。Std::future可以调用get()函数来获取这个异常,并且在获取异常时会重新抛出它,使得异常处理变得更加容易和集中

(3)无论是成功执行任务还是抛出异常,std::packaged_task的共享状态在任务完成后都会被标记为ready。这意味着相关的std::future对象可以访问结果或者异常,并且可以继续执行。

2.调用operator()操作符的效果

调用operator()的行为取决于创建std::packaged_task时指定的可调用对象

(1)当函数指针或者函数对象作为指定的可调用对象时,那么调用operator() 就是简单的将参数传递给这个函数或对象

示例:

#include <iostream>
#include <future>

// 普通函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 将普通函数封装成异步任务
    std::packaged_task<int(int, int)> task(add);

    // 获取 future 来获取任务执行结果
    std::future<int> result = task.get_future();

    // 执行任务,相当于调用 add(2, 3)
    task(2, 3);

    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}

(2).当指向类的非静态成员函数的指针作为可调用的对象时,那么调用operat or()时需要提供一个对象实例作为第一个参数,剩余的参数则作为成员 函数的参数传递

示例:

#include <iostream>
#include <future>

class MyAdd {
public:
    int add(int a, int b) {
        return a + b;
    }
};

int main() {
    // 创建一个 std::packaged_task,封装成员函数指针
    std::packaged_task<int(MyAdd&, int, int)> task(&MyAdd::add);

    MyAdd ma;

    // 获取 future 对象用于获取执行结果
    std::future<int> result = task.get_future();

    // 调用任务,相当于 ma.add(1, 2)
    task(ma, 1, 2);

    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}

(3)当类的非静态成员变量作为可调用对象时,那么operator()只传入一个相应类的对象即可,这个参数是用来访问成员变量的

示例:

#include <iostream>
#include <future>

class MyClass {
public:
    int value;
    MyClass(int v) : value(v) {}
};

int main() {
    MyClass obj(42);

    // 用 lambda 包装一个成员访问函数
    std::packaged_task<int(MyClass&)> task([](MyClass& obj) { return obj.value; });

    std::future<int> result = task.get_future();  // 获取 future 对象

    task(obj);  // 执行任务

    std::cout << "Result: " << result.get() << std::endl;  // 输出结果
}

std::packaged_task::make_ready_at_thread_exit()介绍

1.功能和行为

(1)调用任务:与operator()类似,他会执行包装的任务并处理参数

(2)延迟设置共享状态:与operator()不同的是,make_ready_at_thread_exit并不会立即设置std::packaged_task的共享状态为ready。相反,他会在当前线程即将退出时(即线程的生命周期结束时)设置共享状态的标志为ready,这意味着std::future对象在调用future::get时,如果线程还没有结束,将会被阻塞,直到线程退出,线程退出后,future::get调用会继续执行,可能会返回任务的结果或抛出异常

2.异常处理

如果make_ready_at_thread_exit在当前线程退出之前有其他代码尝试设置或修改共享状态的值(例如在另一个线程通过std::packaged_task的operator()或者其他操作修改了任务的结果),这将导致抛出std::future_error异常,具体异常为std::future_errc::promise_already_satisfied,表示共享状态已经被设置,并且不允许再次设置

std::packaged_task::reset()介绍

重置packaged_task的共享状态(将std::packaged_task对象的共享状态即std::promise的状态重置初始状态),但是保留了之前被包装的任务,表示可以重新使用相同的std::oackaged_task对象来执行新的任务。

示例:

#include <iostream>
#include <future>

// 普通函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 创建 packaged_task 封装 add 函数
    std::packaged_task<int(int, int)> task(add);

    // 第一次执行
    std::future<int> f1 = task.get_future();  // 获取 future
    task(1, 2);                               // 执行任务
    std::cout << "f1 result: " << f1.get() << std::endl;

    // 重置 packaged_task,使其可再次使用
    task.reset();

    // 第二次执行
    std::future<int> f2 = task.get_future();  // 获取新的 future
    task(3, 4);                               // 执行新任务
    std::cout << "f2 result: " << f2.get() << std::endl;

    return 0;
}

std::packaged_task::swap()介绍

交换两个packaged_task对象的共享状态(std::promise对象)和所包装的任务

共享状态是std::packaged_task用来存储异步任务结果的地方,这包括结果值和异常信息

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

int main() {
    std::packaged_task<int(int, int)> task1(add);       // 任务1:加法
    std::packaged_task<int(int, int)> task2(multiply);  // 任务2:乘法

    task1.swap(task2);  // 交换两个任务

    std::future<int> result = task1.get_future();  // 获取 task1 的 future(此时是乘法任务了)
    task1(3, 4);  // 实际调用乘法任务(因为已被交换)

    std::cout << "Result: " << result.get() << std::endl;  // 输出:12
    return 0;
}

五、std::future示例代码

#include <iostream>
#include <thread>
#include <future>
#include <chrono>

// 使用 std::promise 和 std::future 传递数据
void promiseExample() {
    std::promise<int> promiseObj;                         // 创建 promise 对象
    std::future<int> futureObj = promiseObj.get_future(); // 获取与 promise 共享的 future

    std::thread producer([&promiseObj]() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        promiseObj.set_value(42);  // 设置结果值,唤醒 futureObj.get()
    });

    int result = futureObj.get();  // 阻塞等待结果
    std::cout << "[promise] result = " << result << std::endl;

    producer.join();  // 等待线程结束
}

// 使用 std::packaged_task 和 std::future 执行任务
void packagedTaskExample() {
    std::packaged_task<int(int)> task([](int x) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return x * 2;
    });

    std::future<int> futureObj = task.get_future(); // 获取 future
    std::thread worker(std::move(task), 21);        // 启动线程执行任务

    int result = futureObj.get();  // 获取计算结果
    std::cout << "[packaged_task] result = " << result << std::endl;

    worker.join();
}

// 使用 std::async 异步执行任务
void asyncExample() {
    auto futureObj = std::async(std::launch::async, [](int x) {
        return x + 5;
    }, 10);

    int result = futureObj.get();  // 获取异步任务结果
    std::cout << "[async] result = " << result << std::endl;
}

int main() {
    promiseExample();
    packagedTaskExample();
    asyncExample();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值