C++11多線程-異步運行(3)之最終篇(future+async)

前面兩章多次使用到std::future,本章我們就來揭開std::future廬山真面目。最后我們會引出std::async蹄衷,該函數(shù)使得我們的并發(fā)調(diào)用變得簡單赔蒲,優(yōu)雅泌神。

3. std::future

前面我們多次使用std::future的get方法來獲取其它線程的結(jié)果,那么除這個方法外舞虱,std::future還有哪些方法呢欢际。

enum class future_status
{
    ready,
    timeout,
    deferred
};
template <class R>
class future
{
public:
    // retrieving the value
    R get();
    // functions to check state
    bool valid() const noexcept;

    void wait() const;
    template <class Rep, class Period>
    future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;
    template <class Clock, class Duration>
    future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;

    shared_future<R> share() noexcept;
};

以上代碼去掉了std::future構(gòu)造、析構(gòu)矾兜、賦值相關(guān)的代碼损趋,這些約束我們之前都講過了。下面我們來逐一了解上面這些函數(shù)椅寺。

3.1 get

這個函數(shù)我們之前一直使用浑槽,該函數(shù)會一直阻塞蒋失,直到獲取到結(jié)果或異步任務(wù)拋出異常。

3.2 share

std::future允許move括荡,但是不允許拷貝高镐。如果用戶確有這種需求,需要同時持有多個實例畸冲,怎么辦呢? 這就是share發(fā)揮作用的時候了妆档。std::shared_future通過引用計數(shù)的方式實現(xiàn)了多實例共享同一狀態(tài),但有計數(shù)就伴隨著同步開銷(無鎖的原子操作也是有開銷的)幻梯,性能會稍有下降刨仑。因此C++11要求程序員顯式調(diào)用該函數(shù),以表明用戶對由此帶來的開銷負責苫耸。std::shared_future允許move州邢,允許拷貝,并且具有和std::future同樣的成員函數(shù)褪子,此處就不一一介紹了量淌。當調(diào)用share后,std::future對象就不再和任何共享狀態(tài)關(guān)聯(lián)嫌褪,其valid函數(shù)也會變?yōu)閒alse呀枢。

3.3 wait

等待,直到數(shù)據(jù)就緒笼痛。數(shù)據(jù)就緒時裙秋,通過get函數(shù),無等待即可獲得數(shù)據(jù)缨伊。

3.4 wait_for和wait_until

wait_for摘刑、wait_until主要是用來進行超時等待的。wait_for等待指定時長刻坊,wait_until則等待到指定的時間點枷恕。返回值有3種狀態(tài):

  1. ready - 數(shù)據(jù)已就緒,可以通過get獲取了谭胚。
  2. timeout - 超時活尊,數(shù)據(jù)還未準備好。
  3. deferred - 這個和std::async相關(guān)漏益,表明無需wait蛹锰,異步函數(shù)將在get時執(zhí)行。

3.5 valid

判斷當前std::future實例是否有效绰疤。std::future主要是用來獲取異步任務(wù)結(jié)果的铜犬,作為消費方出現(xiàn),單獨構(gòu)建出來的實例沒意義,因此其valid為false癣猾。當與其它生產(chǎn)方(Provider)通過共享狀態(tài)關(guān)聯(lián)后敛劝,valid才會變得有效,std::future才會發(fā)揮實際的作用纷宇。C++11中有下面幾種Provider夸盟,從這些Provider可獲得有效的std::future實例:

  1. std::async
  2. std::promise::get_future
  3. std::packaged_task::get_future

既然std::future的各種行為都依賴共享狀態(tài),那么什么是共享狀態(tài)呢?

4. 共享狀態(tài)

共享狀態(tài)其本質(zhì)就是單生產(chǎn)者-單消費者的多線程并發(fā)模型像捶。無論是std::promise還是std::packaged_task都是通過共享狀態(tài)上陕,實現(xiàn)與std::future通信的。還記得我們在std::condition_variable一節(jié)給出的chan類么拓春。共享狀態(tài)與其類似释簿,通過std::mutex、std::condition_variable實現(xiàn)了多線程間通信硼莽。共享狀態(tài)并非C++11的標準庶溶,只是對std::promise、std::future的實現(xiàn)手段懂鸵∑荩回想我們之前的使用場景,共享狀態(tài)可能具有如下形式(c++11偽代碼):

template<typename T>
class assoc_state {
protected:
    mutable mutex mut_;
    mutable condition_variable cv_;
    unsigned state_ = 0;
    // std::shared_future中拷貝動作會發(fā)生引用計數(shù)的變化
    // 當引用計數(shù)降到0時匆光,實例被delete
    int share_count_ = 0;
    exception_ptr exception_; // 執(zhí)行異常
    T value_;  // 執(zhí)行結(jié)果

public:
    enum {
        ready = 4,  // 異步動作執(zhí)行完砖茸,數(shù)據(jù)就緒
        // 異步動作將延遲到future.get()時調(diào)用
        // (實際上非異步,只不過是延遲執(zhí)行)
        deferred = 8,
    };

    assoc_state() {}
    // 禁止拷貝
    assoc_state(const assoc_state &) = delete;
    assoc_state &operator=(const assoc_state &) = delete;
    // 禁止move
    assoc_state(assoc_state &&) = delete;
    assoc_state &operator=(assoc_state &&) = delete;

    void set_value(const T &);
    void set_exception(exception_ptr p);
    // 需要用到線程局變存儲
    void set_value_at_thread_exit(const T &);
    void set_exception_at_thread_exit(exception_ptr p);

    void wait();
    future_status wait_for(const duration &) const;
    future_status wait_until(const time_point &) const;

    T &get() {
        unique_lock<mutex> lock(this->mut_);
        // 當_state為deferred時殴穴,std::async中
        // 的函數(shù)將在sub_wait中調(diào)用
        this->sub_wait(lock);
        if (this->_exception != nullptr)
            rethrow_exception(this->_exception);
        return _value;
    }
private:
    void sub_wait(unique_lock<mutex> &lk) {
        if (state_ != ready) {
            if (state_ & static_cast<unsigned>(deferred)) {
                state_ &= ~static_cast<unsigned>(deferred);
                lk.unlock();
                __execute();  // 此處執(zhí)行實際的函數(shù)調(diào)用
            } else {
                cv_.wait(lk, [this](){return state == ready;})
            }
        }
    }
};

以上給出了get的實現(xiàn)(偽代碼),其它部分雖然沒實現(xiàn)货葬,但assoc_state應(yīng)該具有的功能采幌,以及對std::promise、std::packaged_task震桶、std::future休傍、std::shared_future的支撐應(yīng)該能夠表達清楚了。未實現(xiàn)部分還請讀者自行補充一下蹲姐,權(quán)當是練手了磨取。
有興趣的讀者可以閱讀llvm-libxx(https://github.com/llvm-mirror/libcxx)的源碼,以了解更多細節(jié)柴墩,對共享狀態(tài)有更深掌握忙厌。

5. std::async

std::async可以看作是對std::packaged_task的封裝(雖然實際并一定如此,取決于編譯器的實現(xiàn)江咳,但共享狀態(tài)的思想是不變的)逢净,有兩種重載形式:

#define FR typename result_of<typename decay<F>::type(typename decay<Args>::type...)>::type

// 不含執(zhí)行策略
template <class F, class... Args>
future<FR> async(F&& f, Args&&... args);
// 含執(zhí)行策略
template <class F, class... Args>
future<FR> async(launch policy, F&& f, Args&&... args);

define部分是用來推斷函數(shù)F的返回值類型,我們先忽略它,以后有機再講爹土。兩個重載形式的差別是一個含執(zhí)行策略甥雕,而另一個不含。那么什么是執(zhí)行策略呢胀茵?執(zhí)行策略定義了async執(zhí)行F(函數(shù)或可調(diào)用求對象)的方式社露,是一個枚舉值:

enum class launch {
    // 保證異步行為,F(xiàn)將在單獨的線程中執(zhí)行
    async = 1,
    // 當其它線程調(diào)用std::future::get時琼娘,
    // 將調(diào)用非異步形式, 即F在get函數(shù)內(nèi)執(zhí)行
    deferred = 2,
    // F的執(zhí)行時機由std::async來決定
    any = async | deferred
};

不含加載策略的版本峭弟,使用的是std::launch::any,也即由std::async函數(shù)自行決定F的執(zhí)行策略轨奄。那么C++11如何確定std::any下的具體執(zhí)行策略呢孟害,一種可能的辦法是:優(yōu)先使用async策略,如果創(chuàng)建線程失敗挪拟,則使用deferred策略挨务。實際上這也是Clang的any實現(xiàn)方式。std::async的出現(xiàn)大大減輕了異步的工作量玉组。使得一個異步調(diào)用可以像執(zhí)行普通函數(shù)一樣簡單谎柄。

#include <iostream> // std::cout, std::endl
#include <future>   // std::async, std::future
#include <chrono>   // seconds
using namespace std::chrono;

int main() {
    auto print = [](char c) {
        for (int i = 0; i < 10; i++) {
            std::cout << c;
            std::cout.flush();
            std::this_thread::sleep_for(milliseconds(1));
        }
    };
    // 不同launch策略的效果
    std::launch policies[] = {std::launch::async, std::launch::deferred};
    const char *names[] = {"async   ", "deferred"};
    for (int i = 0; i < sizeof(policies)/sizeof(policies[0]); i++) {
        std::cout << names[i] << ": ";
        std::cout.flush();
        auto f1 = std::async(policies[i], print, '+');
        auto f2 = std::async(policies[i], print, '-');
        f1.get();
        f2.get();
        std::cout << std::endl;
    }

    return 0;
}

以上代碼輸出如下:

async   : +-+-+-+--+-++-+--+-+
deferred: ++++++++++----------

進行到現(xiàn)在,C++11的async算是結(jié)束了惯雳,盡管還留了一些疑問朝巫,比如共享狀態(tài)如何實現(xiàn)set_value_at_thread_exit效果。我們將會在下一章節(jié)介紹C++11的線程局部存儲石景,順便也解答下該疑問劈猿。

上一篇
C++11多線程-packaged_task
目錄 下一篇
線程局部存儲
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市潮孽,隨后出現(xiàn)的幾起案子揪荣,更是在濱河造成了極大的恐慌,老刑警劉巖往史,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仗颈,死亡現(xiàn)場離奇詭異,居然都是意外死亡椎例,警方通過查閱死者的電腦和手機挨决,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來订歪,“玉大人脖祈,你說我怎么就攤上這事∷⒔” “怎么了撒犀?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵福压,是天一觀的道長。 經(jīng)常有香客問我或舞,道長荆姆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任映凳,我火速辦了婚禮胆筒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诈豌。我一直安慰自己仆救,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布矫渔。 她就那樣靜靜地躺著彤蔽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庙洼。 梳的紋絲不亂的頭發(fā)上顿痪,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音油够,去河邊找鬼蚁袭。 笑死,一個胖子當著我的面吹牛石咬,可吹牛的內(nèi)容都是我干的揩悄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼鬼悠,長吁一口氣:“原來是場噩夢啊……” “哼删性!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起焕窝,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蹬挺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后袜啃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡幸缕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年群发,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片发乔。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡熟妓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出栏尚,到底是詐尸還是另有隱情起愈,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站抬虽,受9級特大地震影響官觅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阐污,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一休涤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笛辟,春花似錦功氨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至围来,卻和暖如春跺涤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背管钳。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工钦铁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人才漆。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓牛曹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親醇滥。 傳聞我的和親對象是個殘疾皇子黎比,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容