【C++併發實戰】(三) std::future和std::promise

leno米雷發表於2019-01-02

std::future和std::promise

std::future

std::future期待一個返回,從一個非同步呼叫的角度來說,future更像是執行函式的返回值,C++標準庫使用std::future為一次性事件建模,如果一個事件需要等待特定的一次性事件,那麼這執行緒可以獲取一個future物件來代表這個事件。
非同步呼叫往往不知道何時返回,但是如果非同步呼叫的過程需要同步,或者說後一個非同步呼叫需要使用前一個非同步呼叫的結果。這個時候就要用到future。
執行緒可以週期性的在這個future上等待一小段時間,檢查future是否已經ready,如果沒有,該執行緒可以先去做另一個任務,一旦future就緒,該future就無法復位(無法再次使用這個future等待這個事件),所以future代表的是一次性事件

future的型別

<future>庫的標頭檔案中宣告瞭兩種future,唯一future(std::future)和共享future(std::shared_future)這兩個是參照std::unique_ptr和std::shared_ptr設立的,前者的例項是僅有的一個指向其關聯事件的例項,而後者可以有多個例項指向同一個關聯事件,當事件就緒時,所有指向同一事件的std::shared_future例項會變成就緒。

future的使用

std::future是一個模板,例如std::future<int>,模板引數就是期待返回的型別,雖然future被用於執行緒間通訊,但其本身卻並不提供同步訪問,熱門必須通過互斥元或其他同步機制來保護訪問。
future使用的時機是當你不需要立刻得到一個結果的時候,你可以開啟一個執行緒幫你去做一項任務,並期待這個任務的返回,但是std::thread並沒有提供這樣的機制,這就需要用到std::async和std::future(都在<future>標頭檔案中宣告)
std::async返回一個std::future物件,而不是給你一個確定的值(所以當你不需要立刻使用此值的時候才需要用到這個機制)。當你需要使用這個值的時候,對future使用get(),執行緒就會阻塞直到future就緒,然後返回該值。

#include <future>
#include <iostream>

int find_result_to_add()
{
    return 1 + 1;
}

void do_other_things() 
{
    std::cout << "Hello World" << std::endl;
}

int main()
{
    std::future<int> result = std::async(find_result_to_add);
    do_other_things();
    std::cout << result.get() << std::endl;
    return 0;
}

跟thread類似,async允許你通過將額外的引數新增到呼叫中,來將附加引數傳遞給函式。如果傳入的函式指標是某個類的成員函式,則還需要將類物件指標傳入(直接傳入,傳入指標,或者是std::ref封裝)。
預設情況下,std::async是否啟動一個新執行緒,或者在等待future時,任務是否同步執行都取決於你給的引數。這個引數為std::launch型別

  • std::launch::defered表明該函式會被延遲呼叫,直到在future上呼叫get()或者wait()為止
  • std::launch::async,表明函式會在自己建立的執行緒上執行
  • std::launch::any = std::launch::defered | std::launch::async
  • std::launch::sync = std::launch::defered
enum class launch
{
    async,deferred,sync=deferred,any=async|deferred
};

PS:預設選項引數被設定為std::launch::any。如果函式被延遲執行可能永遠都不會執行。

std::packaged_task

如果說std::async和std::feature還是分開看的關係的話,那麼std::packaged_task就是將任務和feature繫結在一起的模板,是一種封裝對任務的封裝。

The class template std::packaged_task wraps any Callable target (function, lambda expression, bind expression, or another function object) so that it can be invoked asynchronously. Its return value or exception thrown is stored in a shared state which can be accessed through std::future objects.

可以通過std::packaged_task物件獲取任務相關聯的feature,呼叫get_future()方法可以獲得std::packaged_task物件繫結的函式的返回值型別的future。std::packaged_task的模板引數是函式簽名
PS:例如int add(int a, intb)的函式簽名就是int(int, int)

#include <future>
#include <iostream>

int add(int a, int b)
{
    return a + b;
}

void do_other_things() 
{
    std::cout << "Hello World" << std::endl;
}

int main()
{
    std::packaged_task<int(int, int)> task(add);
    do_other_things();
    std::future<int> result = task.get_future();
    task(1, 1); //必須要讓任務執行,否則在get()獲取future的值時會一直阻塞
    std::cout << result.get() << std::endl;
    return 0;
}

std::promise

從字面意思上理解promise代表一個承諾。promise比std::packaged_task抽象層次低。
std::promise<T>提供了一種設定值的方式,它可以在這之後通過相關聯的std::future<T>物件進行讀取。換種說法,之前已經說過std::future可以讀取一個非同步函式的返回值了,那麼這個std::promise就提供一種方式手動讓future就緒。

#include <future>
#include <string>
#include <thread>
#include <iostream>

void print(std::promise<std::string>& p)
{
    p.set_value("There is the result whitch you want.");
}

void do_some_other_things()
{
    std::cout << "Hello World" << std::endl;
}

int main()
{
    std::promise<std::string> promise;

    std::future<std::string> result = promise.get_future();
    std::thread t(print, std::ref(promise));
    do_some_other_things();
    std::cout << result.get() << std::endl;
    t.join();
    return 0;
}

由此可以看出在promise建立好的時候future也已經建立好了
執行緒在建立promise的同時會獲得一個future,然後將promise傳遞給設定他的執行緒,當前執行緒則持有future,以便隨時檢查是否可以取值。

總結

future的表現為期望,當前執行緒持有future時,期望從future獲取到想要的結果和返回,可以把future當做非同步函式的返回值。而promise是一個承諾,當執行緒建立了promise物件後,這個promise物件向執行緒承諾他必定會被人設定一個值,和promise相關聯的future就是獲取其返回的手段。

相關文章