C++11 future 的 promise

| | 0 Comments| 13:43
Categories:

前一篇基本上針對 C++11 <future>std::packaged_task<> 做了簡單的說明了,接下來,則來簡單紀錄一下 std::promise<>C++ Reference)吧。

首先,在 Heresy 來看,std::promise<> 基本上應該是 std::async()std::packaged_task<> 內部實作時使用的類別;它是設計成可以用來儲存一個值或是例外,然後之後可以透過對應的 std::future<> 來取得資料的類別。

在使用上,取得值的方法很單純,只有 get_future() 這個函式;透過這個函式可以取得 std::promise<> 對應的 std::future<> 物件,可以用來確認 std::promise<> 的狀態、並取得他的值。而如果 std::promise<> 物件是被設定成例外狀況的話,那在呼叫 std::future<>get() 的時候、就會丟出裡面儲存的例外。

而設定  std::promise<> 的值則有兩種,一種是 set_value()、另一種是 set_exception()。前者基本上就是程式運作正常,把計算的結果儲存到 std::promise<> 裡讓對應的 std::future<> 物件可以透過 get() 拿到結果;後者就是在處理過程有問題,所以自這邊設定一個例外狀況、讓之後要取得結果的時候可以收到造成錯誤的例外。

如果要把之前介紹 std::packaged_task<> 時的例子改成用 std::promise<> 的話,在不使用 thread 的時候大概會像下面這樣:

#include <iostream>
#include <future>
 
double compute(double val)
{
  return val * val;
}
 
int main()
{
  // create promise
  std::promise<double> p;
 
  // get future
  std::future<double> f = p.get_future();
 
  // set value
  p.set_value(compute(5));
 
  // get value
  std::cout << f.get() << std::endl;
}

而如果要在另一個執行序做的話,則可以把呼叫 set_value() 的地方改成:

// set value in another thread
std::jthread t(
  [&p]() {
    p.set_value(compute(5));
  });

如果這邊不想透過 lambda 再包一層的,那就需要直接修改 compute() 了。修改過的程式大致上會像下面的樣子:

#include <iostream>
#include <future>
#include <thread>
 
void compute(std::promise<double> p, double val)
{
  p.set_value(val * val);
}
 
int main()
{
  // create promise
  std::promise<double> p;
 
  // get future
  std::future<double> f = p.get_future();
 
  // set value in another thread
  std::jthread t(compute, std::move(p), 5);
 
  // get value
  std::cout << f.get() << std::endl;
}

這邊在基本的範例來看,基本上就是用 std::promise<>set_value() 取代函式裡面的 return,但是實際上也不是所有情境都是這樣就是了。

實際上,這邊也可以使用多個 std::promise<>、或是在 set_value() 後繼續做別的工作;不過,這種情境大概是比較複雜的狀況才會需要了。


至於 set_exception() 該怎麼用呢?基本上,會需要這個功能,主要就是一般狀況下,在額外的執行序丟出來的例外(exception)是沒辦法直接透過 try – catch 抓到的;而如果是透過 std::future<> 的設計的話,則是可以在呼叫 get() 的時候、抓到過程中丟出的例外。

不過真的要能做到,其實就是要在執行的時候去攔截例外、並透過 set_exception() 來告訴 std::promise<> 才行。

比如說上面的程式如果改寫成下面的狀況:

#include <iostream>
#include <future>
#include <thread>
 
double compute(double val)
{
  if (val < 0)
    throw std::runtime_error("should not >= 0");
  return val * val;
}
 
int main()
{
  // create promise
  std::promise<double> p;
 
  // get future
  std::future<double> f = p.get_future();
 
  try
  {
    // set value in another thread
    std::jthread t(
      [&p]() {
        p.set_value(compute(-1));
      });
 
    // get value
    std::cout << f.get() << std::endl;
  }
  catch (const std::exception& e)
  {
    std::cerr << e.what() << "\n";
  }
}

compute() 裡面,如果傳入的參數比 0 小就會丟出例外。而這邊是傳 -1 進去,感覺應該要攔截到丟出來的例外吧?但是實際上這邊因為另外是在另一個執行序產生的,所以主執行序的 try – catch 並沒有辦法攔截到它。

如果要讓他可以攔截到 compute() 丟出來的例外的話,以這個例子來說,是需要在 lambda 裡面額外處理的。這邊比較簡單是可以改成:

// set value in another thread
std::jthread t(
  [&p]() {
    try
    {
      p.set_value(compute(-1));
    }
    catch (...)
    {
      p.set_exception(std::current_exception());
    }
  });

這邊基本上就是去檢查在正常執行的流程中有沒有例外,有的話就把現在的例外透過 set_exception() 丟給 std::promise<>;這樣一來,之後在呼叫 f.get() 的時候,他就會把內部儲存的例外再重新丟出來,這樣就可以正確攔截到例外了!

不過這邊算是省略了一些東西啦~實際上考量到 p 本身的狀態,set_exception() 本身也是可能丟例外出來要另外再接的;所以真的要安全的話,在上面的 catch 裡面應該還得再 try-catch 一次。


另外,除了直接使用 set_X() 來設定外,他也提供了 set_X_at_thread_exit() 的版本,會在執行序結束的時候去設定值。

不過老實說,個人不太清楚這東西什麼時候會用到就是了…

而如果真的要用的話,要注意的是他會在執行序結束的時候、變數都刪除後才會執行;所以如果寫成:

void compute(std::promise<double> p, double val)
{
  p.set_value_at_thread_exit(val * val);
}

這樣的話,在最後要設定值的時候會因為 p 已經被消滅了而失敗。

解決方法是用改用參考的形式:

#include <iostream>
#include <future>
#include <thread>
 
void compute(std::promise<double>& p, double val)
{
  p.set_value_at_thread_exit(val * val);
}
 
int main()
{
  std::promise<double> p;
  std::future<double> f = p.get_future();
  std::jthread t(compute, std::ref(p), 5);
  std::cout << f.get() << std::endl;
}

理論上這樣應該是沒問題的。但是就是得自己管理好 p 的生命週期了。


std::promise<> 的基本使用大概就是這樣了?感覺上,在大部分的狀況下,似乎沒有特別去直接使用他的需求?

但是如果是想在把多個 std::future<> 對應的 std::promise<> 都丟到同一個執行序的時候,或許會是有用的?只是到底什麼時候會有這種需求,個人也還沒想到了。 XD

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *