C++11 std::future, std::promise, std::packaged_task, std::async

本文根據(jù)眾多互聯(lián)網(wǎng)博客內(nèi)容整理后形成,引用內(nèi)容的版權(quán)歸原始作者所有,僅限于學習研究使用,不得用于任何商業(yè)用途。

c++11中增加了線程,使得我們可以非常方便的創(chuàng)建線程,它的基本用法是這樣的:

void f(int n);
std::thread t(f, n + 1);
t.join();

但是線程畢竟是屬于比較低層次的東西,有時候使用有些不便,比如我希望獲取線程函數(shù)的返回結(jié)果的時候,我就不能直接通過thread.join()得到結(jié)果,這時就必須定義一個變量,在線程函數(shù)中去給這個變量賦值,然后join,最后得到結(jié)果,這個過程是比較繁瑣的。c++11還提供了異步接口std::async,通過這個異步接口可以很方便的獲取線程函數(shù)的執(zhí)行結(jié)果。std::async會自動創(chuàng)建一個線程去調(diào)用線程函數(shù),它返回一個std::future,這個future中存儲了線程函數(shù)返回的結(jié)果,當我們需要線程函數(shù)的結(jié)果時,直接從future中獲取,非常方便。但是我想說的是,其實std::async給我們提供的便利可不僅僅是這一點,它首先解耦了線程的創(chuàng)建和執(zhí)行,使得我們可以在需要的時候獲取異步操作的結(jié)果;其次它還提供了線程的創(chuàng)建策略(比如可以通過延遲加載的方式去創(chuàng)建線程),使得我們可以以多種方式去創(chuàng)建線程。在介紹async具體用法以及為什么要用std::async代替線程的創(chuàng)建之前,我想先說一說std::future、std::promise和std::packaged_task。

std::future
std::future是一個非常有用也很有意思的東西,簡單說std::future提供了一種訪問異步操作結(jié)果的機制。從字面意思來理解,它表示未來,我覺得這個名字非常貼切,因為一個異步操作我們是不可能馬上就獲取操作結(jié)果的,只能在未來某個時候獲取,但是我們可以以同步等待的方式來獲取結(jié)果,可以通過查詢future的狀態(tài)(future_status)來獲取異步操作的結(jié)果。future_status有三種狀態(tài):

狀態(tài) 含義
deferred 異步操作還沒開始
ready 異步操作已經(jīng)完成
timeout 異步操作超時
//查詢future的狀態(tài)
std::future_status status;
do {
    status = future.wait_for(std::chrono::seconds(1));
    if (status == std::future_status::deferred) {
        std::cout << "deferred\n";
    } else if (status == std::future_status::timeout) {
        std::cout << "timeout\n";
    } else if (status == std::future_status::ready) {
        std::cout << "ready!\n";
    }
} while (status != std::future_status::ready);

獲取future結(jié)果有三種方式:get、wait、wait_for,其中g(shù)et等待異步操作結(jié)束并返回結(jié)果,wait只是等待異步操作完成,沒有返回值,wait_for是超時等待返回結(jié)果。

std::promise
std::promise為獲取線程函數(shù)中的某個值提供便利,在線程函數(shù)中給外面?zhèn)鬟M來的promise賦值,當線程函數(shù)執(zhí)行完成之后就可以通過promis獲取該值了,值得注意的是取值是間接的通過promise內(nèi)部提供的future來獲取的。它的基本用法:

std::promise<int> pr;
std::thread t([](std::promise<int>& p){ p.set_value_at_thread_exit(9); },std::ref(pr));
std::future<int> f = pr.get_future();
auto r = f.get();

std::packaged_task
std::packaged_task它包裝了一個可調(diào)用的目標(如function, lambda expression, bind expression, or another function object),以便異步調(diào)用,它和promise在某種程度上有點像,promise保存了一個共享狀態(tài)的值,而packaged_task保存的是一個函數(shù)。它的基本用法:

std::packaged_task<int()> task([](){ return 7; });
std::thread t1(std::ref(task));
std::future<int> f1 = task.get_future();
auto r1 = f1.get();

std::promise、std::packaged_task和std::future的關(guān)系
在線程1中創(chuàng)建一個std::promise對象

std::promise<int> promiseObj;
目前為止,該promise對象沒有任何管理的值,但它承諾肯定會有人對其進行賦值,一旦被賦值,就可以通過其管理的std::future對象來獲取該值。
但是,假設(shè)線程1創(chuàng)建了該promise對象并將其傳給線程2,那么線程1怎樣知道線程2什么時候會對promise對象進行賦值呢?
答案是使用std::future對象
每個std::promise對象都有個對應(yīng)的std::future對象,其他人可以通過它來獲取promise設(shè)置的值。
所以,線程1將會創(chuàng)建std::promise對象,然后在將其傳遞給線程2之前從它那里獲取std::future對象。

std::future<int> futureObj = promiseObj.get_future();
現(xiàn)在,線程1將promiseObj傳遞給線程2.
那么線程1將會獲取到線程2通過std::future的get函數(shù)設(shè)置在std::promise中的值,

int val = futureObj.get();
但是如果線程2還沒有對該值進行設(shè)置,那么這個調(diào)用將會阻塞,直到線程2在promise對象中對該值進行設(shè)置。

promiseObj.set_value(45);
查看下圖中的完整流程


image.png

至此, 我們介紹了std::async相關(guān)的幾個對象std::future、std::promise和std::packaged_task,其中std::promise和std::packaged_task的結(jié)果最終都是通過其內(nèi)部的future返回出來的,不知道讀者有沒有搞糊涂,為什么有這么多東西出來,他們之間的關(guān)系到底是怎樣的?且聽我慢慢道來,std::future提供了一個訪問異步操作結(jié)果的機制,它和線程是一個級別的屬于低層次的對象,在它之上高一層的是std::packaged_task和std::promise,他們內(nèi)部都有future以便訪問異步操作結(jié)果,std::packaged_task包裝的是一個異步操作,而std::promise包裝的是一個值,都是為了方便異步操作的,因為有時我需要獲取線程中的某個值,這時就用std::promise,而有時我需要獲一個異步操作的返回值,這時就用std::packaged_task。那std::promise和std::packaged_task之間又是什么關(guān)系呢?說他們沒關(guān)系也關(guān)系,說他們有關(guān)系也有關(guān)系,都取決于你了,因為我可以將一個異步操作的結(jié)果保存到std::promise中。如果讀者還沒搞清楚他們的關(guān)系的話,我就用更通俗的話來解釋一下。比如,一個小伙子給一個姑娘表白真心的時候也許會說:”我許諾會給你一個美好的未來“或者”我會努力奮斗為你創(chuàng)造一個美好的未來“。姑娘往往會說:”我等著“?,F(xiàn)在我來將這三句話用c++11來翻譯一下:

小伙子說:我許諾會給你一個美好的未來等于c++11中"std::promise a std::future";
小伙子說:我會努力奮斗為你創(chuàng)造一個美好的未來等于c++11中"std::packaged_task a future";
姑娘說:我等著等于c++11中"future.get()/wait()";

小伙子兩句話的個中差異,自己琢磨一下,這點差異也是std::promise和std::packaged_task的差異。
示例1:

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

void Thread_Fun1(std::promise<int> &p)
{    
    //為了突出效果,可以使線程休眠5s
    std::this_thread::sleep_for(std::chrono::seconds(5));
    
    int iVal = 233;
    std::cout << "傳入數(shù)據(jù)(int):" << iVal << std::endl;
    
    //傳入數(shù)據(jù)iVal    
    p.set_value(iVal);
} 

void Thread_Fun2(std::future<int> &f){
    //阻塞函數(shù),直到收到相關(guān)聯(lián)的std::promise對象傳入的數(shù)據(jù)
    auto iVal = f.get();    //iVal = 233
    
    std::cout << "收到數(shù)據(jù)(int):" << iVal << std::endl;
} 

int main(){    
    //聲明一個std::promise對象pr1,其保存的值類型為int
    std::promise<int> pr1;    
    //聲明一個std::future對象fu1,并通過std::promise的get_future()函數(shù)與pr1綁定
    std::future<int> fu1 = pr1.get_future();
    //創(chuàng)建一個線程t1,將函數(shù)Thread_Fun1及對象pr1放在線程里面執(zhí)行
    std::thread t1(Thread_Fun1, std::ref(pr1));

    //創(chuàng)建一個線程t2,將函數(shù)Thread_Fun2及對象fu1放在線程里面執(zhí)行
    std::thread t2(Thread_Fun2, std::ref(fu1));     //阻塞至線程結(jié)束

    t1.join();
    t2.join();

    return 1;
}

可以看到std::future對象fu1先是通過std::promise的函數(shù)get_future()與std::promise對象pr1相綁定,pr1在線程t1中通過set_value()傳入共享數(shù)據(jù),fu1在線程t2中通過阻塞函數(shù)get()獲取到傳入的數(shù)據(jù)。
示例1中傳入的數(shù)據(jù)類型是int,前面介紹中說std::promise可以保存typename T的數(shù)據(jù),那么可以保存函數(shù)指針嗎?答案是可行的,請看示例。

示例2:

#include <iostream>
#include <future>
#include <chrono>
#include <functional> 

//聲明一個可調(diào)對象Tusing 
T = std::function<int(int)>;    //等同于typedef std::function<int(int)> T; 

int Test_Fun(int iVal)
{
    std::cout << "Value is:" << iVal << std::endl;
    return iVal + 232;
} 

void Thread_Fun1(std::promise<T> &p)
{    
    //為了突出效果,可以使線程休眠5s
    std::this_thread::sleep_for(std::chrono::seconds(5));

    std::cout << "傳入函數(shù)Test_Fun" << std::endl;

    //傳入函數(shù)Test_Fun
    p.set_value(std::bind(&Test_Fun, std::placeholders::_1));
} 

void Thread_Fun2(std::future<T> &f)
{    
    //阻塞函數(shù),直到收到相關(guān)聯(lián)的std::promise對象傳入的數(shù)據(jù)
    auto fun = f.get();        //iVal = 233

    int iVal = fun(1);

    std::cout << "收到函數(shù)并運行,結(jié)果:" << iVal << std::endl;
} 

int main()
{
    //聲明一個std::promise對象pr1,其保存的值類型為int
    std::promise<T> pr1;    
    //聲明一個std::future對象fu1,并通過std::promise的get_future()函數(shù)與pr1綁定
    std::future<T> fu1 = pr1.get_future();

    //創(chuàng)建一個線程t1,將函數(shù)Thread_Fun1及對象pr1放在線程里面執(zhí)行
    std::thread t1(Thread_Fun1, std::ref(pr1));    
    
    //創(chuàng)建一個線程t2,將函數(shù)Thread_Fun2及對象fu1放在線程里面執(zhí)行
    std::thread t2(Thread_Fun2, std::ref(fu1));

    //阻塞至線程結(jié)束
    t1.join();
    t2.join();
    return 1;
}

既然可以傳函數(shù)對象,那么是否可以通過模板魔改,傳入可變元函數(shù)?請看示例。

示例3:

#include <iostream>
#include <future>
#include <chrono>
#include <functional> 

//聲明一個可調(diào)對象Fusing F = std::function<int(int, int, int&)>;        //等同于typedef std::function<int(int, int, int&)> F; 

//函數(shù)可以改成任意參數(shù),任意返回類型int Test_Fun(int a, int b, int &c)
{
    //a = 1, b = 2    c = a + b + 230;
    return c;
}

void Thread_Fun1(std::promise<F> &p)
{    
    //為了突出效果,可以使線程休眠5s
    std::this_thread::sleep_for(std::chrono::seconds(5));

    std::cout << "傳入函數(shù)Test_Fun" << std::endl;

    //傳入函數(shù)Test_Fun
    p.set_value(std::bind(&Test_Fun, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
} 

template<typename T, typename ...Args>
void Thread_Fun2(std::future<T> &f, Args&& ...args)
{
    //阻塞函數(shù),直到收到相關(guān)聯(lián)的std::promise對象傳入的數(shù)據(jù)
    auto fun = f.get();        //fun等同于Test_Fun
    
    auto fResult = fun(std::forward<Args>(args)...);

    std::cout << "收到函數(shù)并運行,結(jié)果:" << fResult << std::endl;
} 

int main()
{    

    //聲明一個std::promise對象pr1,其保存的值類型為int
    std::promise<F> pr1;
    //聲明一個std::future對象fu1,并通過std::promise的get_future()函數(shù)與pr1綁定
    std::future<F> fu1 = pr1.get_future();     
    
    //聲明一個變量
    int iVal = 0;     
    
    //創(chuàng)建一個線程t1,將函數(shù)Thread_Fun1及對象pr1放在線程里面執(zhí)行
    std::thread t1(Thread_Fun1, std::ref(pr1));
    //創(chuàng)建一個線程t2,將函數(shù)Thread_Fun2及對象fu1放在線程里面執(zhí)行
    std::thread t2(Thread_Fun2<F, int, int, int&>, std::ref(fu1), 1, 2, std::ref(iVal));

    //阻塞至線程結(jié)束
    t1.join();
    t2.join();
    //此時iVal的值變成233

    return 1;
}

為什么要用std::async代替線程的創(chuàng)建
std::async又是干啥的,已經(jīng)有了td::future、std::promise和std::packaged_task,夠多的了,真的還要一個std::async來湊熱鬧嗎,std::async表示很委屈:我不是來湊熱鬧的,我是來幫忙的。是的,std::async是為了讓用戶的少費點腦子的,它讓這三個對象默契的工作。大概的工作過程是這樣的:std::async先將異步操作用std::packaged_task包裝起來,然后將異步操作的結(jié)果放到std::promise中,這個過程就是創(chuàng)造未來的過程。外面再通過future.get/wait來獲取這個未來的結(jié)果,怎么樣,std::async真的是來幫忙的吧,你不用再想到底該怎么用std::future、std::promise和std::packaged_task了,std::async已經(jīng)幫你搞定一切了!

現(xiàn)在來看看std::async的原型async(std::launch::async | std::launch::deferred, f, args...),第一個參數(shù)是線程的創(chuàng)建策略,有兩種策略,默認的策略是立即創(chuàng)建線程:

std::launch::async:在調(diào)用async就開始創(chuàng)建線程。
std::launch::deferred:延遲加載方式創(chuàng)建線程。調(diào)用async時不創(chuàng)建線程,直到調(diào)用了future的get或者wait時才創(chuàng)建線程。

第二個參數(shù)是線程函數(shù),第三個參數(shù)是線程函數(shù)的參數(shù)。

std::async基本用法:

std::future<int> f1 = std::async(std::launch::async, [](){
    return 8;
});

cout<<f1.get()<<endl; //output: 8

std::future<int> f2 = std::async(std::launch::async, [](){
    cout<<8<<endl;
    });

    f2.wait(); //output: 8

std::future<int> future = std::async(std::launch::async, [](){
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 8;
});

std::cout << "waiting...\n";
std::future_status status;

do {
    status = future.wait_for(std::chrono::seconds(1));
    if (status == std::future_status::deferred) {
        std::cout << "deferred\n";
    } else if (status == std::future_status::timeout) {
        std::cout << "timeout\n";
    } else if (status == std::future_status::ready) {
        std::cout << "ready!\n";
    }
} while (status != std::future_status::ready);

std::cout << "result is " << future.get() << '\n';

可能的結(jié)果:
waiting...
timeout
timeout
ready!
result is 8

std::async是更高層次上的異步操作,使我們不用關(guān)注線程創(chuàng)建內(nèi)部細節(jié),就能方便的獲取異步執(zhí)行狀態(tài)和結(jié)果,還可以指定線程創(chuàng)建策略,應(yīng)該用std::async替代線程的創(chuàng)建,讓它成為我們做異步操作的首選。

參考文獻
C++11之std::future和std::promise
[C++11]std::promise介紹及使用
(原創(chuàng))用C++11的std::async代替線程的創(chuàng)建
c++11多線程編程(八):std::future , std::promise和線程的返回值

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容