C++ 11新特性:std::future & std::shared_future) (轉載)
上一講《C++11 併發指南四(<future> 詳解二 std::packaged_task 介紹)》主要介紹了 <future> 標頭檔案中的 std::packaged_task 類,本文主要介紹 std::future,std::shared_future 以及 std::future_error,另外還會介紹 <future> 標頭檔案中的 std::async,std::future_category 函式以及相關列舉型別。
std::future 介紹
前面已經多次提到過 std::future,那麼 std::future 究竟是什麼呢?簡單地說,std::future 可以用來獲取非同步任務的結果,因此可以把它當成一種簡單的執行緒間同步的手段。std::future 通常由某個 Provider 建立,你可以把 Provider 想象成一個非同步任務的提供者,Provider 在某個執行緒中設定共享狀態的值,與該共享狀態相關聯的 std::future 物件呼叫 get(通常在另外一個執行緒中) 獲取該值,如果共享狀態的標誌不為 ready,則呼叫 std::future::get 會阻塞當前的呼叫者,直到 Provider 設定了共享狀態的值(此時共享狀態的標誌變為 ready),std::future::get 返回非同步任務的值或異常(如果發生了異常)。
一個有效(valid)的 std::future 物件通常由以下三種 Provider 建立,並和某個共享狀態相關聯。Provider 可以是函式或者類,其實我們前面都已經提到了,他們分別是:
- std::async 函式,本文後面會介紹 std::async() 函式。
- std::promise::get_future,get_future 為 promise 類的成員函式,詳見 C++11 併發指南四(<future> 詳解一 std::promise 介紹)。
- std::packaged_task::get_future,此時 get_future為 packaged_task 的成員函式,詳見
一個 std::future 物件只有在有效(valid)的情況下才有用(useful),由 std::future 預設建構函式建立的 future 物件不是有效的(除非當前非有效的 future 物件被 move 賦值另一個有效的 future 物件)。
在一個有效的 future 物件上呼叫 get 會阻塞當前的呼叫者,直到 Provider 設定了共享狀態的值或異常(此時共享狀態的標誌變為 ready),std::future::get 將返回非同步任務的值或異常(如果發生了異常)。
// future example
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds
// a non-optimized way of checking for prime numbers:
bool
is_prime(int x)
{
for (int i = 2; i < x; ++i)
if (x % i == 0)
return false;
return true;
}
int
main()
{
// call function asynchronously:
std::future < bool > fut = std::async(is_prime, 444444443);
// do something while waiting for function to set future:
std::cout << "checking, please wait";
std::chrono::milliseconds span(100);
while (fut.wait_for(span) == std::future_status::timeout)
std::cout << '.';
bool x = fut.get(); // retrieve return value
std::cout << "\n444444443 " << (x ? "is" : "is not") << " prime.\n";
return 0;
}
std::future 成員函式
std::future 建構函式
std::future 一般由 std::async, std::promise::get_future, std::packaged_task::get_future 建立,不過也提供了建構函式,如下表所示:
default (1) |
future() noexcept; |
---|---|
copy [deleted] (2) |
future (const future&) = delete; |
move (3) |
future (future&& x) noexcept; |
不過 std::future 的拷貝建構函式是被禁用的,只提供了預設的建構函式和 move 建構函式(注:C++ 新特新)。另外,std::future 的普通賦值操作也被禁用,只提供了 move 賦值操作。如下程式碼所示:
std::future<int> fut; // 預設建構函式
fut = std::async(do_some_task); // move-賦值操作。
std::future::share()
返回一個 std::shared_future 物件(本文後續內容將介紹 std::shared_future ),呼叫該函式之後,該 std::future 物件本身已經不和任何共享狀態相關聯,因此該 std::future 的狀態不再是 valid 的了。
#include <iostream> // std::cout
#include <future> // std::async, std::future, std::shared_future
int do_get_value() { return 10; }
int main ()
{
std::future<int> fut = std::async(do_get_value);
std::shared_future<int> shared_fut = fut.share();
// 共享的 future 物件可以被多次訪問.
std::cout << "value: " << shared_fut.get() << '\n';
std::cout << "its double: " << shared_fut.get()*2 << '\n';
return 0;
}
std::future::get()
std::future::get 一共有三種形式,如下表所示(參考):
generic template (1) |
T get(); |
---|---|
reference specialization (2) |
R& future<R&>::get(); // when T is a reference type (R&) |
void specialization (3) |
void future<void>::get(); // when T is void |
當與該 std::future 物件相關聯的共享狀態標誌變為 ready 後,呼叫該函式將返回儲存在共享狀態中的值,如果共享狀態的標誌不為 ready,則呼叫該函式會阻塞當前的呼叫者,而此後一旦共享狀態的標誌變為 ready,get 返回 Provider 所設定的共享狀態的值或者異常(如果丟擲了異常)。
請看下面的程式:
#include <iostream> // std::cin, std::cout, std::ios
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
#include <exception> // std::exception, std::current_exception
void get_int(std::promise<int>& prom) {
int x;
std::cout << "Please, enter an integer value: ";
std::cin.exceptions (std::ios::failbit); // throw on failbit
try {
std::cin >> x; // sets failbit if input is not int
prom.set_value(x);
} catch (std::exception&) {
prom.set_exception(std::current_exception());
}
}
void print_int(std::future<int>& fut) {
try {
int x = fut.get();
std::cout << "value: " << x << '\n';
} catch (std::exception& e) {
std::cout << "[exception caught: " << e.what() << "]\n";
}
}
int main ()
{
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread th1(get_int, std::ref(prom));
std::thread th2(print_int, std::ref(fut));
th1.join();
th2.join();
return 0;
}
std::future::valid()
檢查當前的 std::future 物件是否有效,即釋放與某個共享狀態相關聯。一個有效的 std::future 物件只能通過 std::async(), std::future::get_future 或者 std::packaged_task::get_future 來初始化。另外由 std::future 預設建構函式建立的 std::future 物件是無效(invalid)的,當然通過 std::future 的 move 賦值後該 std::future 物件也可以變為 valid。
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <utility> // std::move
int do_get_value() { return 11; }
int main ()
{
// 由預設建構函式建立的 std::future 物件,
// 初始化時該 std::future 物件處於為 invalid 狀態.
std::future<int> foo, bar;
foo = std::async(do_get_value); // move 賦值, foo 變為 valid.
bar = std::move(foo); // move 賦值, bar 變為 valid, 而 move 賦值以後 foo 變為 invalid.
if (foo.valid())
std::cout << "foo's value: " << foo.get() << '\n';
else
std::cout << "foo is not valid\n";
if (bar.valid())
std::cout << "bar's value: " << bar.get() << '\n';
else
std::cout << "bar is not valid\n";
return 0;
}
std::future::wait()
等待與當前std::future 物件相關聯的共享狀態的標誌變為 ready.
如果共享狀態的標誌不是 ready(此時 Provider 沒有在共享狀態上設定值(或者異常)),呼叫該函式會被阻塞當前執行緒,直到共享狀態的標誌變為 ready。
一旦共享狀態的標誌變為 ready,wait() 函式返回,當前執行緒被解除阻塞,但是 wait() 並不讀取共享狀態的值或者異常。下面的程式碼說明了 std::future::wait() 的用法(參考)
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds
// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 為了體現效果, 該函式故意沒有優化.
{
for (int i = 2; i < x; ++i)
if (x % i == 0)
return false;
return true;
}
int main()
{
// call function asynchronously:
std::future < bool > fut = std::async(do_check_prime, 194232491);
std::cout << "Checking...\n";
fut.wait();
std::cout << "\n194232491 ";
if (fut.get()) // guaranteed to be ready (and not block) after wait returns
std::cout << "is prime.\n";
else
std::cout << "is not prime.\n";
return 0;
}
執行結果如下:
concurrency ) ./Future-wait
Checking...
194232491 is prime.
concurrency )
std::future::wait_for()
與 std::future::wait() 的功能類似,即等待與該 std::future 物件相關聯的共享狀態的標誌變為 ready,該函式原型如下:
template <class Rep, class Period>
future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;
而與 std::future::wait() 不同的是,wait_for() 可以設定一個時間段 rel_time,如果共享狀態的標誌在該時間段結束之前沒有被 Provider 設定為 ready,則呼叫 wait_for 的執行緒被阻塞,在等待了 rel_time 的時間長度後 wait_until() 返回,返回值如下:
返回值 | 描述 |
---|---|
future_status::ready | 共享狀態的標誌已經變為 ready,即 Provider 在共享狀態上設定了值或者異常。 |
future_status::timeout | 超時,即在規定的時間內共享狀態的標誌沒有變為 ready。 |
future_status::deferred | 共享狀態包含一個deferred 函式。 |
請看下面的例子:
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds
// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 為了體現效果, 該函式故意沒有優化.
{
for (int i = 2; i < x; ++i)
if (x % i == 0)
return false;
return true;
}
int main()
{
// call function asynchronously:
std::future < bool > fut = std::async(do_check_prime, 194232491);
std::cout << "Checking...\n";
std::chrono::milliseconds span(1000); // 設定超時間隔.
// 如果超時,則輸出".",繼續等待
while (fut.wait_for(span) == std::future_status::timeout)
std::cout << '.';
std::cout << "\n194232491 ";
if (fut.get()) // guaranteed to be ready (and not block) after wait returns
std::cout << "is prime.\n";
else
std::cout << "is not prime.\n";
return 0;
}
std::future::wait_until()
與 std::future::wait() 的功能類似,即等待與該 std::future 物件相關聯的共享狀態的標誌變為 ready,該函式原型如下:
template <class Rep, class Period>
future_status wait_until (const chrono::time_point<Clock,Duration>& abs_time) const;
而 與 std::future::wait() 不同的是,wait_until() 可以設定一個系統絕對時間點 abs_time,如果共享狀態的標誌在該時間點到來之前沒有被 Provider 設定為 ready,則呼叫 wait_until 的執行緒被阻塞,在 abs_time 這一時刻到來之後 wait_for() 返回,返回值如下:
返回值 | 描述 |
---|---|
future_status::ready | 共享狀態的標誌已經變為 ready,即 Provider 在共享狀態上設定了值或者異常。 |
future_status::timeout | 超時,即在規定的時間內共享狀態的標誌沒有變為 ready。 |
future_status::deferred | 共享狀態包含一個deferred 函式。 |
std::shared_future 介紹
std::shared_future 與 std::future 類似,但是 std::shared_future 可以拷貝、多個 std::shared_future 可以共享某個共享狀態的最終結果(即共享狀態的某個值或者異常)。shared_future 可以通過某個 std::future 物件隱式轉換(參見 std::shared_future 的建構函式),或者通過 std::future::share() 顯示轉換,無論哪種轉換,被轉換的那個 std::future 物件都會變為 not-valid.
std::shared_future 建構函式
std::shared_future 共有四種建構函式,如下表所示:
default (1) |
shared_future() noexcept; |
---|---|
copy (2) |
shared_future (const shared_future& x); |
move (3) |
shared_future (shared_future&& x) noexcept; |
move from future (4) |
shared_future (future<T>&& x) noexcept; |
最後 move from future(4) 即從一個有效的 std::future 物件構造一個 std::shared_future,構造之後 std::future 物件 x 變為無效(not-valid)。
std::shared_future 其他成員函式
std::shared_future 的成員函式和 std::future 大部分相同,如下(每個成員函式都給出了連線):
- operator=
- 賦值操作符,與 std::future 的賦值操作不同,std::shared_future 除了支援 move 賦值操作外,還支援普通的賦值操作。
- get
- 獲取與該 std::shared_future 物件相關聯的共享狀態的值(或者異常)。
- valid
- 有效性檢查。
- wait
- 等待與該 std::shared_future 物件相關聯的共享狀態的標誌變為 ready。
- wait_for
- 等待與該 std::shared_future 物件相關聯的共享狀態的標誌變為 ready。(等待一段時間,超過該時間段wait_for 返回。)
- wait_until
- 等待與該 std::shared_future 物件相關聯的共享狀態的標誌變為 ready。(在某一時刻前等待,超過該時刻 wait_until 返回。)
std::future_error 介紹
class future_error : public logic_error;
std::future_error 繼承子 C++ 標準異常體系中的 logic_error,有關 C++ 異常的繼承體系,請參考相關的C++教程 ;-)。
其他與 std::future 相關的函式介紹
與 std::future 相關的函式主要是 std::async(),原型如下:
unspecified policy (1) |
template <class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> |
---|---|
specific policy (2) |
template <class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> |
上面兩組 std::async() 的不同之處是第一類 std::async 沒有指定非同步任務(即執行某一函式)的啟動策略(launch policy),而第二類函式指定了啟動策略,詳見 std::launch 列舉型別,指定啟動策略的函式的 policy 引數可以是launch::async,launch::deferred,以及兩者的按位或( | )。
std::async() 的 fn 和 args 引數用來指定非同步任務及其引數。另外,std::async() 返回一個 std::future 物件,通過該物件可以獲取非同步任務的值或異常(如果非同步任務丟擲了異常)。
下面介紹一下 std::async 的用法。
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <chrono>
#include <future>
#include <iostream>
double ThreadTask(int n) {
std::cout << std::this_thread::get_id()
<< " start computing..." << std::endl;
double ret = 0;
for (int i = 0; i <= n; i++) {
ret += std::sin(i);
}
std::cout << std::this_thread::get_id()
<< " finished computing..." << std::endl;
return ret;
}
int main(int argc, const char *argv[])
{
std::future<double> f(std::async(std::launch::async, ThreadTask, 100000000));
#if 0
while(f.wait_until(std::chrono::system_clock::now() + std::chrono::seconds(1))
!= std::future_status::ready) {
std::cout << "task is running...\n";
}
#else
while(f.wait_for(std::chrono::seconds(1))
!= std::future_status::ready) {
std::cout << "task is running...\n";
}
#endif
std::cout << f.get() << std::endl;
return EXIT_SUCCESS;
}
其他與 std::future 相關的列舉類介紹
下面介紹與 std::future 相關的列舉型別。與 std::future 相關的列舉型別包括:
enum class future_errc;
enum class future_status;
enum class launch;
下面分別介紹以上三種列舉型別:
std::future_errc 型別
std::future_errc 型別描述如下(參考):
型別 | 取值 | 描述 |
---|---|---|
broken_promise | 0 |
與該 std::future 共享狀態相關聯的 std::promise 物件在設定值或者異常之前一被銷燬。 |
future_already_retrieved | 1 |
與該 std::future 物件相關聯的共享狀態的值已經被當前 Provider 獲取了,即呼叫了 std::future::get 函式。 |
promise_already_satisfied | 2 |
std::promise 物件已經對共享狀態設定了某一值或者異常。 |
no_state | 3 |
無共享狀態。 |
std::future_status 型別(參考)
std::future_status 型別主要用在 std::future(或std::shared_future)中的 wait_for 和 wait_until 兩個函式中的。
型別 | 取值 | 描述 |
---|---|---|
future_status::ready | 0 |
wait_for(或wait_until) 因為共享狀態的標誌變為 ready 而返回。 |
future_status::timeout | 1 |
超時,即 wait_for(或wait_until) 因為在指定的時間段(或時刻)內共享狀態的標誌依然沒有變為 ready 而返回。 |
future_status::deferred | 2 |
共享狀態包含了 deferred 函式。 |
std::launch 型別
該列舉型別主要是在呼叫 std::async 設定非同步任務的啟動策略的。
型別 | 描述 |
---|---|
launch::async | Asynchronous: 非同步任務會在另外一個執行緒中呼叫,並通過共享狀態返回非同步任務的結果(一般是呼叫 std::future::get() 獲取非同步任務的結果)。 |
launch::deferred | Deferred: 非同步任務將會在共享狀態被訪問時呼叫,相當與按需呼叫(即延遲(deferred)呼叫)。 |
請看下例(參考):
#include <iostream> // std::cout
#include <future> // std::async, std::future, std::launch
#include <chrono> // std::chrono::milliseconds
#include <thread> // std::this_thread::sleep_for
void
do_print_ten(char c, int ms)
{
for (int i = 0; i < 10; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
std::cout << c;
}
}
int
main()
{
std::cout << "with launch::async:\n";
std::future < void >foo =
std::async(std::launch::async, do_print_ten, '*', 100);
std::future < void >bar =
std::async(std::launch::async, do_print_ten, '@', 200);
// async "get" (wait for foo and bar to be ready):
foo.get();
bar.get();
std::cout << "\n\n";
std::cout << "with launch::deferred:\n";
foo = std::async(std::launch::deferred, do_print_ten, '*', 100);
bar = std::async(std::launch::deferred, do_print_ten, '@', 200);
// deferred "get" (perform the actual calls):
foo.get();
bar.get();
std::cout << '\n';
return 0;
}
在我的機器上執行結果:
with launch::async:
*@**@**@**@**@*@@@@@
with launch::deferred:
**********@@@@@@@@@@