c++11并發(fā)庫之線程同步

  • 主要內(nèi)容
    • 條件變量
    • future
    • async/packeged_task/promise
    • shared_future

條件變量

std::mutex _mutex;
std::condition_variable _cv;
std::deque<std::string> _data;

void thread_process_data()
{
    while(1){
        std::unique_lock<std::mutex> lk(_mutex);
        _cv.wait(lk, [](){return !_data.empty();});
        std::string str = _data.front();
        _data.pop();
        lk.unlock();
        do_something(str);
    }
}

void thread_produce_data()
{
    while(1){
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        {
            std::lock_guard<std::mutex> lk(_mutex);
            _data.push("hello cv");
        }
        _cv.notify_one();
    }  
}

等待條件的線程處理流程:

  • 獲取一個std::unique_lock<std::mutex>用來保護(hù)共享變量
  • 執(zhí)行wait(wait_forwait_until)并级,這些函數(shù)的調(diào)用將以原子方式釋放互斥鎖并暫停線程執(zhí)行
    • 在釋放互斥鎖之前彻舰,這里會第一次檢查lambda返回值巧骚;
    • 如果條件真,則線程從wait返回并繼續(xù)執(zhí)行画髓;
    • 如果條件假系吩,則線程阻塞并同時釋放互斥鎖;
  • 當(dāng)條件變量被通知、超時過期或出現(xiàn)虛假喚醒時線程被喚醒液肌,互斥鎖被原子地重新獲取。然后鸥滨,如果喚醒是假的嗦哆,線程應(yīng)該檢查條件并繼續(xù)等待。
    • 喚醒時重新獲取互斥鎖
    • 再次檢查lambda返回值
    • 如果條件真婿滓,則線程繼續(xù)執(zhí)行老速;
    • 如果條件假,則線程阻塞并同時釋放互斥鎖凸主;

使用std::unique_lock而不是std::lock_guard原因有二:

1橘券、等待的線程必須在等待時解鎖互斥鎖,并在等待之后再次鎖定它卿吐,而std::lock_guard不提供這種靈活性.

2旁舰、wait()的第一個參數(shù)要求的就是std::unique_lock的引用。

喚醒等待條件的線程:

  • 獲取std::lock_gurad<std::mutex>
  • 執(zhí)行修改
  • 執(zhí)行notify_onenotify_all通知條件發(fā)生但两,等待條件的線程將被喚醒(通知不需要持有鎖)

condition variable適用于某個事件會反復(fù)的發(fā)生的情景鬓梅,在某些情景下線程只想等待一次事件為真,之后它將永遠(yuǎn)不會再等待這個事件發(fā)生谨湘。

future

async與future

async相比thread的不同:

  • std::thread是一個類模板绽快,而std::async只是一個函數(shù)模板
  • std::async返回std::future對象芥丧,讓異步操作創(chuàng)建者訪問異步結(jié)果.
  • 調(diào)用std::thread總啟動一個新線程,且在新線程中立即執(zhí)行f
  • 調(diào)用std::async不一定會啟動一個新線程坊罢,并且可以決定是否立即執(zhí)行f续担、延遲執(zhí)行、不執(zhí)行

async的policy參數(shù)

  • 設(shè)置為launch::async, 將在新線程中立即執(zhí)行f
  • 設(shè)置為launch::deferred活孩,async的f將延期執(zhí)行直到future對象被查詢(get/wait)物遇,并且f將會在查詢future對象的線程中執(zhí)行,這個線程甚至不一定是調(diào)用async的線程憾儒;如果future對象從未被查詢询兴,f將永遠(yuǎn)不會執(zhí)行。
  • 設(shè)置為launch::deferred |std::launch::async,與實(shí)現(xiàn)有關(guān)起趾,可能立即異步執(zhí)行诗舰,也可能延遲執(zhí)行。
  • launch::deferred和std::launch::async都沒設(shè)置训裆,C++14中為未定義
packaged_task與future
  • 類模板std::packaged_task包裝任何Callable(函數(shù)眶根、lambda、bind表達(dá)式或其他函數(shù)對象)边琉。
  • 與std::function不同的是属百,std::packaged_task提供了std::future.
  • 其威力在于只要能夠訪問std::future,無論std::packaged_task作為對象被傳輸?shù)侥膫€線程中執(zhí)行变姨,都可以通過std::future獲取其結(jié)果族扰。
  • 實(shí)例化的std::packaged_task本身也是一個Callable。它可以包裝在std::function對象中钳恕,也可以作為線程函數(shù)傳遞給std::thread别伏,或者傳遞給另一個需要Callable的函數(shù),甚至可以被直接調(diào)用忧额。
  • std::packaged_task可以用作線程池或者任務(wù)隊列的構(gòu)建塊,作為“消息”在線程之間傳遞愧口。

包裝lambda

void task_lambda()
{
    std::packaged_task<int(int,int)> task([](int a, int b) {
        return std::pow(a, b); 
    });
    std::future<int> result = task.get_future();
    //在當(dāng)前線程執(zhí)行std::pow(2,9)
    task(2, 9);
    std::cout << "task_lambda:\t" << result.get() << '\n';
}

包裝bind

int f(int x, int y) { return std::pow(x,y); }
void task_bind()
{
    std::packaged_task<int(int,int)> task(bind(f,1,2));
    std::future<int> result = task.get_future();
    //在當(dāng)前線程執(zhí)行std::pow(2,9)
    task(2, 9);
    std::cout << "task_bind:\t" << result.get() << '\n';
}

作為線程函數(shù)

int f(int x, int y) { return std::pow(x,y); }
void task_thread()
{
    std::packaged_task<int(int,int)> task(f);
    std::future<int> result = task.get_future();
    //在新線程執(zhí)行std::pow(2,9)
    std::thread t(std::move(task), 2, 9);
    std::cout << "task_thread:\t" << result.get() << '\n';
}

等同于:

void equal_to_task_thread()
{
    std::future<int> result = std::async(std::launch::async, f, 2, 9);
    std::cout << "equal_to_async:\t" << result.get() << '\n';
}
promise與future

std::promise<T>提供了一種設(shè)置值(類型為T)的方法睦番,稍后可以通過關(guān)聯(lián)的std::future對象讀取該值。配對使用futurepromise耍属,等待結(jié)果的線程可能會阻塞在future::wait()或者future::get()托嚣,而提供數(shù)據(jù)的線程可以使用配對的promise來設(shè)置相關(guān)的值并使future就緒。

一般的范式是:

  • 想要獲取結(jié)果類型為T的線程構(gòu)造std::promise< T >厚骗,并通過promise::get_future獲取future對象

  • 將promise作為參數(shù)傳遞給新的線程函數(shù)示启,在新的線程函數(shù)中通過promise::set_value設(shè)置結(jié)果

  • 等待的線程通過future::wait等待結(jié)果誕生,通過future::get獲取結(jié)果领舰。

    void do_work(arg, std::promise<T> promise_arg)
    {
        do_something();
        promise_arg.set_value(T);  
    }
      
    int main()
    {
        //使用promise<T>在線程之間傳遞結(jié)果
        std::promise<T> one_promise;
        //構(gòu)造future對象
        std::future<T> one_future = one_promise.get_future();
        //promise作為參數(shù)傳遞給新的線程函數(shù)
        std::thread work_thread(do_work, some_arg, std::move(one_promise));
        //等待結(jié)果
        one_future.wait();  
        //獲取結(jié)果
        std::cout << "result=" << one_future.get() << '\n';
        work_thread.join();  
    }
    
  • promise不可拷貝夫嗓,不能直接將one_promise作為參數(shù)傳遞給新的線程函數(shù)迟螺。

異常與future
  • 當(dāng)使用std::async、std::packeged_task時舍咖,如果線程函數(shù)函數(shù)矩父、被包裝的函數(shù)拋出異常,與future關(guān)聯(lián)的數(shù)據(jù)中將 儲存異常 而非結(jié)果排霉。

  • 對于std::promise有點(diǎn)不一樣窍株,如果想存儲異常而非正常的結(jié)果,使用set_exception()替代set_value攻柠。

    extern std::promise<double> some_promise;
    
    try
    {
      some_promise.set_value(calculate_value());
    }
    catch(...)
    {
        //1使用current_exception提取calculate_value()中拋出的異常
        //2使用set_exception()儲存異常
      some_promise.set_exception(std::current_exception());
        //或者
        //some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));
    }
    
  • 當(dāng)異常被儲存球订,future將就緒,通過調(diào)用future::get()將重新拋出存儲的異常瑰钮。

  • 待續(xù)

std::shared_future
  • std::future處理了線程之間傳輸數(shù)據(jù)所需的所有同步辙售,但是對std::future特定實(shí)例的成員函數(shù)調(diào)用不是線程安全的——本身如果從多個線程訪問單個std::future對象而不進(jìn)行額外的同步,那么將面臨數(shù)據(jù)競爭和未定義的行為飞涂。

    std::future 模型對異步結(jié)果有唯一所有權(quán)旦部,只有一個線程可以提取到異步結(jié)果(通過get),在第一次調(diào)用get()之后较店,就已經(jīng)沒有要提取的值了士八。

  • std::shared_future可以讓多個線程可以等待相同的事件

    std::future是可以moveable,對異步結(jié)果的唯一所有權(quán)可以在future實(shí)例之間轉(zhuǎn)移(通過移動構(gòu)造語義)梁呈,但是每次只有一個實(shí)例引用特定的異步結(jié)果婚度。但是std::shared_future實(shí)例是copyable,所以可以有多個shared_future對象引用相同的關(guān)聯(lián)狀態(tài).

  • 單個shared_future對象上的成員函數(shù)仍然不同步官卡,從多個線程訪問單個對象時蝗茁,為了避免數(shù)據(jù)沖突,必須使用鎖來保護(hù)訪問

    使用它的首選方法是獲取對象的副本寻咒,并讓每個線程訪問自己的副本哮翘。如果每個線程通過自己的std::shared_future對象訪問共享異步狀態(tài),那么從多個線程訪問該狀態(tài)是安全的毛秘。

  • 通過future實(shí)例構(gòu)造shared_future實(shí)例饭寺,必須轉(zhuǎn)移所有權(quán)的方式進(jìn)行

    std::promise<bool> p;
    std::future<bool> f = p.get_future();
    
    //構(gòu)造shared_future方式一:
    std::shared_future<bool> sf(f);               //error
    std::shared_future<bool> sf(std::move(f));    //move觸發(fā)移動構(gòu)造
    //方式二:
    std::shared_future<bool> sf = f.share();
    //方式三:
    std::shared_future<bool> sf(std::move(p.get_future()));
    
    assert(!f.valid());
    assert(sf.valid()); 
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叫挟,隨后出現(xiàn)的幾起案子艰匙,更是在濱河造成了極大的恐慌,老刑警劉巖抹恳,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件员凝,死亡現(xiàn)場離奇詭異,居然都是意外死亡奋献,警方通過查閱死者的電腦和手機(jī)健霹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門旺上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骤公,你說我怎么就攤上這事抚官。” “怎么了阶捆?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵凌节,是天一觀的道長。 經(jīng)常有香客問我洒试,道長倍奢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任垒棋,我火速辦了婚禮卒煞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叼架。我一直安慰自己畔裕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布乖订。 她就那樣靜靜地躺著扮饶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乍构。 梳的紋絲不亂的頭發(fā)上甜无,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音哥遮,去河邊找鬼岂丘。 笑死,一個胖子當(dāng)著我的面吹牛眠饮,可吹牛的內(nèi)容都是我干的奥帘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼君仆,長吁一口氣:“原來是場噩夢啊……” “哼翩概!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起返咱,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牍鞠,沒想到半個月后咖摹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡难述,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年萤晴,在試婚紗的時候發(fā)現(xiàn)自己被綠了吐句。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡店读,死狀恐怖嗦枢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屯断,我是刑警寧澤文虏,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站殖演,受9級特大地震影響氧秘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趴久,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一丸相、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彼棍,春花似錦灭忠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坎吻,卻和暖如春缆蝉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘦真。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工刊头, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诸尽。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓原杂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親您机。 傳聞我的和親對象是個殘疾皇子穿肄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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