首页 > C++多线程:异步操作std::async和std::promise

C++多线程:异步操作std::async和std::promise

文章目录

      • std::async
        • 简介
        • 使用案例
      • std::promise
        • 简介
        • 成员函数
      • 总结


之前的文章中提到了C++多线程中的异步操作机制 C++ 多线程:future 异步访问类(线程之间安全便捷的数据共享),接下来分享关于异步操作中 asyncpromise的相关使用总结。

std::async

简介

  • 头文件
  • 使用方式

    st::async( Function&& f, Args&&... args );或者async( std::launch policy, Function&& f, Args&&... args );
  • 简介

    一般它的执行方式为我们以上说的两种

    a. 第一种就是参数为函数名称以及需要传入的函数参数,此时async会开出对应行函数线程,并使用的默认的策略方式std::launch::async | std::launch::deferred,这种策略标示async产生的线程有两种执行方式:一种为线程独立执行,另一种为当主线程或者调用者线程中执行std::future::get的成员函数时会执行产生的线程。

    b. 另一种执行方式为显示声明执行策略policy ,以参数 args 调用函数 f

    若设置 async 标志(即 std::async(std::launch::async,f,x)形式),则在async初始化所有线程局域对象之后会执行函数f。

    若设置的是deferred标志(即std::async(std::launch::deferred,f,x)),则async同样会使用std:thread构造函数的方式转换fargs参数,但是并不会产生执行线程。此时它会进行惰性求值,即当async函数返回的std::future对象进行get取值的时候才会执行线程获取结果。
  • 返回值

    返回std::async 所创建的共享状态的对象 std::future

使用案例

查看如下代码

#include 
#include 
#include 
#include 
#include 
#include 
#include std::mutex m;
struct X { void foo(int i, const std::string& str) { std::lock_guard<std::mutex> lk(m);std::cout << str << ' ' << i << '
';}void bar(const std::string& str) { std::lock_guard<std::mutex> lk(m);std::cout << str << '
';}int operator()(int i) { std::lock_guard<std::mutex> lk(m);std::cout << i << '
';return i + 10;}
};template <typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{ auto len = end - beg;if (len < 1000)return std::accumulate(beg, end, 0);RandomIt mid = beg + len/2;auto handle = std::async(std::launch::async,parallel_sum<RandomIt>, mid, end);int sum = parallel_sum(beg, mid);return sum + handle.get();
}int main()
{ std::vector<int> v(10000, 1);std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '
';X x;// 以默认策略调用 x.foo(42, "Hello") :// 可能同时打印 "Hello 42" 或延迟执行,这里策略默认是std::launch::async|std::launch::deferred//即a1的打印可能在主线程打印的任何时候auto a1 = std::async(&X::foo, &x, 42, "Hello");// 以 deferred 策略调用 x.bar("world!")// 调用 a2.get() 或 a2.wait() 时打印 "world!"auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");// 以 async 策略调用 X()(43) :// 同时打印 "43"auto a3 = std::async(std::launch::async, X(), 43);a2.wait();                     // 打印 "world!"std::cout << a3.get() << '
'; // 打印 "53"
} // 若 a1 在此点未完成,则 a1 的析构函数在此打印 "Hello 42"

因为a1可能是立即执行,也有可能是惰性求值,所以a1对象的打印可能遍布整个打印的不同时间段。

a2则是惰性求值,当调用a2.wait()或者a2.get()求值的时候会获取a2的函数返回值

a3同样为异步求值,同时也能够支持get,只是get()会晚于线程执行之后。

输出如下:

第一种

he sum is 10000
world!
Hello 42
43
53

第二种

The sum is 10000
43
Hello 42
world!
53

第三种

The sum is 10000
Hello 42
43
world!
53

std::promise

简介

  • 头文件
  • 定义

    template< class R > class promise空模版

    template< class R > class promise 非void特化,用于线程之间交流对象

    template<> class promise用于交流无状态事件
  • 简介

    类模板 std::promise 提供存储值或异常的处理措施,之后通过 std::promise 对象所创建的 std::future 对象异步获得结果。注意 std::promise 只应当使用一次。

    每个 promise 与共享状态关联,共享状态含有一些状态信息和可能仍未求值的结果,其中promise对共享状态做三件事:
    • 就绪: promise 存储结果或异常于共享状态。标记共享状态为就绪,并解除阻塞任何等待于与该共享状态关联的 future 上的线程。
    • 释放: promise 放弃其对共享状态的引用。若这是最后一个这种引用,则销毁共享状态。除非这是 std::async 所创建的未就绪的共享状态,否则此操作不阻塞。
    • 抛弃: promise 存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 类型异常,令共享状态为就绪,然后释放它

成员函数

  • 构造函数

    a.promise();默认构造函数,构造一个共享状态为空的 std::promise

    b.template< class Alloc > promise( std::allocator_arg_t, const Alloc& alloc ) 构造一个共享状态为空的 std::promise,由 alloc 分配共享状态

    c.promise( promise&& other ) noexcept移动构造函数,用原属 other 的共享状态构造新的 std::promise 对象,使用移动语义。构造完毕后, other 无共享状态;

    d.promise( const promise& other ) = delete; std::promise 不可复制

  • 析构函数

    ~promise()

    两种情况调用析构函数

    a.若共享状态就绪,则释放它。

    b.若共享状态未就绪,则存储以 std::future_errc::broken_promise 为 error_condition 的 std::future_error 类型异常对象,令共享状态就绪再释放它。

  • 赋值运算符

    promise& operator=( promise&& other ) noexcept

    移动赋值运算符。首先析构原共享状态,然后如同以执行std::promise(std::move(other)).swap(*this)对共享状态赋

    promise& operator=( const promise& rhs ) = delete std::promise 不可复制赋值

  • std::promise::get_future

    返回与 *this 关联同一状态的 future 对象

  • std::promise::set_value更新 promise 对象时获得单个与 promise 对象关联的互斥量, 若无共享状态或共享状态已存储值或异常,则抛出异常。对此函数的调用和对 get_future 的调用不会造成数据竞争。

    查看如下代码,此时并不会造成对共享变量对竞争。

    #include 
    #include 
    #include 
    #include using namespace std;int fun1(std::future<int> &f) { int res = 1;int n = f.get();for (int i = n; i>1; --i) { res *=i;}cout << "Result is " << res << endl;return res;
    }int main()
    { int x;//std::thread t1(fun1,4,std::ref(x));//t1.join();std::promise<int> p;//标示f是一个需要从未来获取数值future对象std::future<int> f = p.get_future(); std::future<int> fu = std::async(std::launch::async,fun1, std::ref(f));//为f设置数值,在子线程中进行f.get()获取主线程到数值p.set_value(4);x = fu.get();cout << "Get from child " << x << endl;return 0;
    }
    

    输出如下:

    Result is 24
    Get from child 24
    
  • std::promise::set_value_at_thread_exit原子地存储 value 到共享状态,而不立即令状态就绪。在当前线程退出时,销毁所有拥有线程局域存储期的对象后,再令状态就绪。即当创建的线程执行结束之前设置共享变量返回调用线程。

    代码如下:

    #include 
    #include 
    #include int main()
    { //using namespace std::chrono_literals;std::promise<int> p;std::future<int> f = p.get_future();//在线程执行结束要离开之前设置共享状态,设置之前等待1s//并返回给线程调用者主线程中的f.wait获取值std::thread([&p] { std::this_thread::sleep_for(std::chrono::seconds(1));p.set_value_at_thread_exit(9);}).detach();std::cout << "Waiting..." << std::flush;f.wait();std::cout << "Done!
    Result is: " << f.get() << '
    ';
    }
    

    输出如下:

    Waiting...Done!
    Result is: 9
    
  • std::promise::set_exception存储异常指针 p 到共享状态中,并令状态就绪

    主要用来进行线程异常情况的存储,同时将异常情况传出到调用线程进行处理

    如下代码

    #include 
    #include 
    #include int main()
    { std::promise<int> p;std::future<int> f = p.get_future();std::thread t([&p]{ try { // 可能抛出的代码throw std::runtime_error("Example");} catch(...) { try { // 存储任何抛出的异常于 promise,设置异常值到共享状态并传出p.set_exception(std::current_exception());} catch(...) { } // set_exception() 亦可能抛出}});try { //获取一异常的共享状态std::cout << f.get();} catch(const std::exception& e) { std::cout << "Exception from the thread: " << e.what() << '
    ';}t.join();
    }
    

    输出如下:

    Exception from the thread: Example

  • std::promise::set_exception_at_thread_exit存储异常指针 p 到共享状态中,而不立即使状态就绪。在当前线程退出时,销毁所有拥有线程局域存储期的变量后,再零状态就绪。

总结

async提供异步操作,可以支持线程异步执行或者惰性求值来达到对线程执行情况以及共享变量获取时机的控制。即我想要获取变量,使用asyncstd::launch::deferred让线程future对象想要获取线程函数结果时再进行线程的执行返回,在此期间线程函数即可处于休眠,依此提供异步线程共享变量机制。

promise类则提供一种共享状态的访问机制,多线程之间的状态共享可以通过promise类对象的get_future监控数据的共享状态,set_value以及set_value_exit等成员函数设置数据的共享状态且内部保证不会产生对共享数据的竞争问题,依此来提供安全使用便捷的多线程之间数据的访问机制。

更多相关:

  • 经过长期探索,发现一个不需要手动设置线程休眠时间(e.g. std::this_thread::sleep_for(std::chrono::microseconds(1)))的代码: Github: https://github.com/log4cplus/ThreadPool #ifndef THREAD_POOL_H_7e...

  • nth_element(first,nth,last) first,last 第一个和最后一个迭代器,也可以直接用数组的位置。  nth,要定位的第nn 个元素,能对它进行随机访问. 将第n_thn_th 元素放到它该放的位置上,左边元素都小于它,右边元素都大于它. 测试代码: http://www.cplusplus.com...

  • c/c++老版本的rand()存在一定的问题,在转换rand随机数的范围,类型或者分布时,常常会引入非随机性。 定义在 中的随机数库通过一组协作类来解决这类问题:随机数引擎 和 随机数分布类 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将(引擎和分布对象)定义为 st...

  • jsoncpp 是一个C++ 语言实现的json库,非常方便得支持C++得各种数据类型到json 以及 json到各种数据类型的转化。 一个json 类型的数据如下: {"code" : 10.01,"files" : "","msg" : "","uploadid" : "UP000000" } 这种数据类型方便我们人阅读以...

  • 问题如下: 已知一组数(其中有重复元素),求这组数可以组成的所有子集中,子 集中的各个元素和为整数target的子集,结果中无重复的子集。 例如: nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8 结果为: [[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]] 同样之前有...

  • Promise 在 JavaScript 中很早就有各种的开源实现,ES6 将其纳入了官方标准,提供了原生 api 支持,使用更加便捷。 定义 Promise 是一个对象,它用来标识 JavaScript 中异步操作的状态(pending, resolve, reject)及结果(data)。 从控制台打印出来一个Promise 对象...

  • 1.Promise 基础知识梳理   创建一个Promise实例 const promise = new Promise(function(resolve, reject) {if (success){resolve(value);} else {reject(error);} }); Promise构造函数接受一个函数作为参数...

  • 在 Vue.js项目中使用Vuex,Vuex 依赖 Promise,所以如果你的浏览器没有实现 Promise (比如 IE),那么就需要使用一个 polyfill 的库 我们可以通过babel-profill转译 1、安装 npm install --save-dev babel-polyfill 2、在main.js中引入...

  • 关于如何在有噪声的数据中进行状态估计的问题的理解,状态估计的问题是指在运动和观测方程中,通常假设两个噪声ωiomega_i和υk,jupsilon_{k,j}满足零均值的高斯分布, xk=f(xk−1,uk)+ωkx_k=f(x_{k-1},u_k)+omega_k其中ωk→N(0,Rk)omega_k ightarro...

  • 强化学习(英语:Reinforcement learning,简称RL)是机器学习中的一个领域,强调如何基于环境而行动,以取得最大化的预期利益。其灵感来源于心理学中的行为主义理论,即有机体如何在环境给予的奖励或惩罚的刺激下,逐步形成对刺激的预期,产生能获得最大利益的习惯性行为。这个方法具有普适性,因此在其他许多领域都有研究,例如博弈...

  • 文章目录PG 的状态机和peering过程1. PG 状态机变化的时机2. pg的状态演化过程3. pg状态变化实例讲解3.1 pg状态的管理结构3.2 数据的pg状态变化过程3.2.1 NULL -> initial3.2.2 initial -> reset -> Started3.2.3 Started(start) ->St...

  • 什么是状态模式? 定义:将事物内部的每个状态分别封装成类,内部状态改变会产生不同行为。 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。 何时使用:代码中包含大量与对象状态有关的条件语句。 如何解决:将各种具体的状态类抽象出来。 应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超...

  • 别小看这个功能, 感觉在写一些技术 Blog 的情况下还是挺有用的.   打开QQ拼音: 输入法设置->基本设置->初始状态->中文状态下使用英文标点.  转载于:https://www.cnblogs.com/qrlozte/p/4904087.html...