C++11多線(xiàn)程-異步運(yùn)行(1)之std::promise

前面介紹了C++11的std::thread、std::mutex以及std::condition_variable,并實(shí)現(xiàn)了一個(gè)多線(xiàn)程通信的chan類(lèi),雖然由于篇幅的限制,該實(shí)現(xiàn)有些簡(jiǎn)陋,甚至有些缺陷,但對(duì)于一般情況應(yīng)該還是夠用了。在C++11多線(xiàn)程系列的最后會(huì)獻(xiàn)上chan的最終版本,敬請(qǐng)期待。
本文將介紹C++11的另一大特性:異步運(yùn)行(std::async)。async顧名思義是將一個(gè)函數(shù)A移至另一線(xiàn)程中去運(yùn)行。A可以是靜態(tài)函數(shù)、全局函數(shù),甚至類(lèi)成員函數(shù)。在異步運(yùn)行的過(guò)程中,如果A需要向調(diào)用者輸出結(jié)果怎么辦呢?std::async完美解決了這一問(wèn)題。在了解async的解決之道前,我們需要一些知識(shí)儲(chǔ)備,那就是:std::promise、std::packaged_task和std::future。異步運(yùn)行涉及的內(nèi)容較多,我們會(huì)分幾節(jié)來(lái)講。

1. std::promise

std::promise是一個(gè)模板類(lèi): template<typename R> class promise。其泛型參數(shù)R為std::promise對(duì)象保存的值的類(lèi)型,R可以是void類(lèi)型。std::promise保存的值可被與之關(guān)聯(lián)的std::future讀取,讀取操作可以發(fā)生在其它線(xiàn)程。std::promise允許move語(yǔ)義(右值構(gòu)造,右值賦值),但不允許拷貝(拷貝構(gòu)造、賦值),std::future亦然。std::promise和std::future合作共同實(shí)現(xiàn)了多線(xiàn)程間通信。

1.1 設(shè)置std::promise的值

通過(guò)成員函數(shù)set_value可以設(shè)置std::promise中保存的值,該值最終會(huì)被與之關(guān)聯(lián)的std::future::get讀取到。需要注意的是:set_value只能被調(diào)用一次,多次調(diào)用會(huì)拋出std::future_error異常。事實(shí)上std::promise::set_xxx函數(shù)會(huì)改變std::promise的狀態(tài)為ready,再次調(diào)用時(shí)發(fā)現(xiàn)狀態(tài)已要是reday了,則拋出異常。

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

void read(std::future<std::string> *future) {
    // future會(huì)一直阻塞,直到有值到來(lái)
    std::cout << future->get() << std::endl;
}

int main() {
    // promise 相當(dāng)于生產(chǎn)者
    std::promise<std::string> promise;
    // future 相當(dāng)于消費(fèi)者, 右值構(gòu)造
    std::future<std::string> future = promise.get_future();
    // 另一線(xiàn)程中通過(guò)future來(lái)讀取promise的值
    std::thread thread(read, &future);
    // 讓read等一會(huì)兒:)
    std::this_thread::sleep_for(seconds(1));
    // 
    promise.set_value("hello future");
    // 等待線(xiàn)程執(zhí)行完成
    thread.join();

    return 0;
}
// 控制臺(tái)輸: hello future

與std::promise關(guān)聯(lián)的std::future是通過(guò)std::promise::get_future獲取到的,自己構(gòu)造出來(lái)的無(wú)效。一個(gè)std::promise實(shí)例只能與一個(gè)std::future關(guān)聯(lián)共享狀態(tài),當(dāng)在同一個(gè)std::promise上反復(fù)調(diào)用get_future會(huì)拋出future_error異常。
共享狀態(tài)。在std::promise構(gòu)造時(shí),std::promise對(duì)象會(huì)與共享狀態(tài)關(guān)聯(lián)起來(lái),這個(gè)共享狀態(tài)可以存儲(chǔ)一個(gè)R類(lèi)型的值或者一個(gè)由std::exception派生出來(lái)的異常值。通過(guò)std::promise::get_future調(diào)用獲得的std::future與std::promise共享相同的共享狀態(tài)。

1.2 當(dāng)std::promise不設(shè)置值時(shí)

如果promise直到銷(xiāo)毀時(shí),都未設(shè)置過(guò)任何值,則promise會(huì)在析構(gòu)時(shí)自動(dòng)設(shè)置為std::future_error,這會(huì)造成std::future.get拋出std::future_error異常。

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

void read(std::future<int> future) {
    try {
        future.get();
    } catch(std::future_error &e) {
        std::cerr << e.code() << "\n" << e.what() << std::endl;
    }
}

int main() {
    std::thread thread;
    {
        // 如果promise不設(shè)置任何值
        // 則在promise析構(gòu)時(shí)會(huì)自動(dòng)設(shè)置為future_error
        // 這會(huì)造成future.get拋出該異常
        std::promise<int> promise;
        thread = std::thread(read, promise.get_future());
    }
    thread.join();

    return 0;
}

上面的程序在Clang下輸出:

future:4
The associated promise has been destructed prior to the associated state becoming ready.

1.3 通過(guò)std::promise讓std::future拋出異常

通過(guò)std::promise::set_exception函數(shù)可以設(shè)置自定義異常,該異常最終會(huì)被傳遞到std::future,并在其get函數(shù)中被拋出。

#include <iostream>
#include <future>
#include <thread>
#include <exception>  // std::make_exception_ptr
#include <stdexcept>  // std::logic_error

void catch_error(std::future<void> &future) {
    try {
        future.get();
    } catch (std::logic_error &e) {
        std::cerr << "logic_error: " << e.what() << std::endl;
    }
}

int main() {
    std::promise<void> promise;
    std::future<void> future = promise.get_future();

    std::thread thread(catch_error, std::ref(future));
    // 自定義異常需要使用make_exception_ptr轉(zhuǎn)換一下
    promise.set_exception(
        std::make_exception_ptr(std::logic_error("caught")));
    
    thread.join();
    return 0;
}
// 輸出:logic_error: caught

std::promise雖然支持自定義異常,但它并不直接接受異常對(duì)象:

// std::promise::set_exception函數(shù)原型
void set_exception(std::exception_ptr p);

自定義異常可以通過(guò)位于頭文件exception下的std::make_exception_ptr函數(shù)轉(zhuǎn)化為std::exception_ptr。

1.4 std::promise<void>

通過(guò)上面的例子,我們看到std::promise<void>是合法的。此時(shí)std::promise.set_value不接受任何參數(shù),僅用于通知關(guān)聯(lián)的std::future.get()解除阻塞。

1.5 std::promise所在線(xiàn)程退出時(shí)

std::async(異步運(yùn)行)時(shí),開(kāi)發(fā)人員有時(shí)會(huì)對(duì)std::promise所在線(xiàn)程退出時(shí)間比較關(guān)注。std::promise支持定制線(xiàn)程退出時(shí)的行為:

  • std::promise::set_value_at_thread_exit 線(xiàn)程退出時(shí),std::future收到通過(guò)該函數(shù)設(shè)置的值。
  • std::promise::set_exception_at_thread_exit 線(xiàn)程退出時(shí),std::future則拋出該函數(shù)指定的異常。

關(guān)于std::promise就是這些,本文從使用角度介紹了std::promise的能力以及邊界,讀者如果想更深入了解該類(lèi),可以直接閱讀一下源碼。

上一篇
C++11多線(xiàn)程-條件變量
目錄 下一篇
C++11多線(xiàn)程-packaged_task
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址。本文參考http:/...
    jorion閱讀 14,844評(píng)論 1 5
  • 接著上節(jié) atomic,本節(jié)主要介紹condition_varible的內(nèi)容,練習(xí)代碼地址。本文參考http://...
    jorion閱讀 8,539評(píng)論 0 7
  • 線(xiàn)程的產(chǎn)生 多線(xiàn)程并發(fā)高級(jí)接口std::async()和類(lèi)std::future<> 1,async()使得可調(diào)用...
    龍遁流閱讀 1,005評(píng)論 0 1
  • 寫(xiě)項(xiàng)目的時(shí)候經(jīng)常會(huì)遇到按鈕上有圖片和文字的情況,每次圖片和按鈕的位置都會(huì)有些變化,經(jīng)常要調(diào),沒(méi)有難點(diǎn),卻很麻煩,所...
    黯魂粉玉閱讀 206評(píng)論 0 0
  • 在許多論壇里都看到過(guò)“什么東西吃了不會(huì)胖?”諸如此類(lèi)的問(wèn)題,一路看下來(lái)基本都是“西北風(fēng)”、“奶嘴”,甚至“蛔蟲(chóng)”得...
    仙貝君閱讀 10,426評(píng)論 14 75