走進C++11(二十七) 處理未來發生的事情 std::future
這一節可能是C++11最難說明白的一節。
其實future有兩個兄弟,一個是std::future, 一個是它大哥std::shared_future。他們的區別就是std::future只支援移動語義,它所引用的共享狀態不與另一非同步返回物件共享。換成人話就是如果你想再多個執行緒共享一個future,那麼你應該用std::shared_future,換成大白話就是你要是想多個執行緒等待一個future,那麼你應該用std::shared_future。如果還是不明白,文章最後會給一個小例子說明。這篇文章主要講的是std::future。
同時講std::future離不開它的承載者和建立者, 也就是std::async,std::packaged_task,std::promise,會在今後的文章一一描述。
首先我們先看std::future是如何定義的(如果感覺枯燥可略過這節):
定義於標頭檔案 <future> | ||
template< class T > class future; | (1) | (C++11 起) |
template< class T > class future<T&>; | (2) | (C++11 起) |
template<> class future<void>; | (3) | (C++11 起) |
類模板 std::future 提供訪問非同步操作結果的機制:
-
(通過 std::async 、 std::packaged_task 或 std::promise 建立的)非同步操作能提供一個 std::future 物件給該非同步操作的建立者。
-
然後,非同步操作的建立者能用各種方法查詢、等待或從 std::future 提取值。若非同步操作仍未提供值,則這些方法可能阻塞。
-
非同步操作準備好傳送結果給建立者時,它能通過修改連結到建立者的 std::future 的共享狀態(例如 std::promise::set_value )進行。
成員函式
(建構函式) | 構造 future 物件 (公開成員函式) |
(解構函式) | 析構 future 物件 (公開成員函式) |
operator= | 移動future物件 (公開成員函式) |
share | 從 *this 轉移共享狀態給 shared_future 並返回它(公開成員函式) |
獲取結果 | |
get | 返回結果 (公開成員函式) |
狀態 | |
valid | 檢查 future 是否擁有共享狀態 (公開成員函式) |
wait | 等待結果變得可用 (公開成員函式) |
wait_for | 等待結果,如果在指定的超時間隔後仍然無法得到結果,則返回。(公開成員函式) |
wait_until | 等待結果,如果在已經到達指定的時間點時仍然無法得到結果,則返回。(公開成員函式) |
std::future涉及到了並行程式設計的概念。為了更好的理解,我們先討論一個我們會經常遇到的場景。現在你要做三件事,一個是燉排骨一個是燒開水,同時你還在追最火的電視劇。那麼作為一個正常人,你不會傻傻的先燒開水,等水燒完了燉排骨,等排骨好了再去看電視劇吧。一個正常人大概率會選擇燉上排骨燒上水後就去看電視劇,然後不斷的檢視排骨和水是不是好了。
當然排骨和水的處理方式不一樣,燒熱水等開了會響,而排骨就要根據個人經驗還有排骨當前的狀態放入佐料並且控制時間。這就涉及到我們程式設計中的架構模型,std::future要討論的就是排骨這種情況,而不是燒水。
在程式設計的過程中,程式中往往會有一些功能或者很耗時或者很佔計算資源,這樣的程式我們往往傾向於讓它在另一個thread中執行,我們的主thread可以先幹其他事情,等幹完了其他事情在來看看之前的工作幹完了沒有,如果幹完了,那麼拿出結果,如果沒幹完就等它幹完或者只等一會。
下邊就舉一個例子:
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
int main()
{
std::future<int> future = std::async(std::launch::async, [](){
std::this_thread::sleep_for(std::chrono::seconds(3));
return 8;
});
std::cout<<"開始燉排骨...\n";
std::future_status status;
do {
// 乾點別的活,比如看電視劇
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred\n";
} else if (status == std::future_status::timeout) {
std::cout<<"一分鐘以後排骨還沒好\n";
} else if (status == std::future_status::ready) {
std::cout << "排骨好了!\n";
}
} while (status != std::future_status::ready);
std::cout << "result is " << future.get() << '\n';
}
可能的輸出:
開始燉排骨...
一分鐘以後排骨還沒好
一分鐘以後排骨還沒好
排骨好了!
result is 8
當然在實踐中我們不會死等,我們會隔一會看看排骨好沒好。
std::shared_future
類模板 std::shared_future 提供訪問非同步操作結果的機制,類似 std::future ,除了允許多個執行緒等候同一共享狀態。不同於僅可移動的 std::future (故只有一個例項能指代任何特定的非同步結果),std::shared_future 可複製而且多個 shared_future 物件能指代同一共享狀態。
若每個執行緒通過其自身的 shared_future 物件副本訪問,則從多個執行緒訪問同一共享狀態是安全的。
shared_future不是文章的重點,這裡僅僅舉一個例子說明:
#include <iostream>
#include <future>
#include <chrono>
int main()
{
std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
std::shared_future<void> ready_future(ready_promise.get_future());
std::chrono::time_point<std::chrono::high_resolution_clock> start;
auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t1_ready_promise.set_value();
ready_future.wait(); // 等待來自 main() 的訊號
return std::chrono::high_resolution_clock::now() - start;
};
auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t2_ready_promise.set_value();
ready_future.wait(); // 等待來自 main() 的訊號
return std::chrono::high_resolution_clock::now() - start;
};
auto result1 = std::async(std::launch::async, fun1);
auto result2 = std::async(std::launch::async, fun2);
// 等待執行緒變為就緒
t1_ready_promise.get_future().wait();
t2_ready_promise.get_future().wait();
// 執行緒已就緒,開始時鐘
start = std::chrono::high_resolution_clock::now();
// 向執行緒發信使之執行
ready_promise.set_value();
std::cout << "Thread 1 received the signal "
<< result1.get().count() << " ms after start\n"
<< "Thread 2 received the signal "
<< result2.get().count() << " ms after start\n";
}
可能的輸出:
Thread 1 received the signal 0.072 ms after start
Thread 2 received the signal 0.041 ms after start