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


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

std::async

简介
  • 头文件 <future>
  • 使用方式
    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 <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>
#include <string>
#include <mutex>
 
std::mutex m;
struct X {
    void foo(int i, const std::string& str) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << ' ' << i << '\n';
    }
    void bar(const std::string& str) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }
    int operator()(int i) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << i << '\n';
        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()) << '\n';
 
    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() << '\n'; // 打印 "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

简介
  • 头文件<future>
  • 定义
    template< class R > class promise空模版
    template< class R > class promise<R&> 非void特化,用于线程之间交流对象
    template<> class promise<void>用于交流无状态事件
  • 简介
    类模板 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<R>::get_future
    返回与 *this 关联同一状态的 future 对象

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

    #include <iostream>
    #include <thread>
    #include <future>
    #include <mutex>
    
    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<R>::set_value_at_thread_exit原子地存储 value 到共享状态,而不立即令状态就绪。在当前线程退出时,销毁所有拥有线程局域存储期的对象后,再令状态就绪。即当创建的线程执行结束之前设置共享变量返回调用线程。
    代码如下:

    #include <iostream>
    #include <future>
    #include <thread>
     
    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!\nResult is: " << f.get() << '\n';
    }
    

    输出如下:

    Waiting...Done!
    Result is: 9
    
  • std::promise<R>::set_exception存储异常指针 p 到共享状态中,并令状态就绪
    主要用来进行线程异常情况的存储,同时将异常情况传出到调用线程进行处理
    如下代码

    #include <thread>
    #include <iostream>
    #include <future>
     
    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() << '\n';
        }
        t.join();
    }
    

    输出如下:
    Exception from the thread: Example

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

总结

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值