前面介紹了C++11的std::thread、std::mutex以及std::condition_variable,并實現了一個多線程通信的chan類,雖然由于篇幅的限制,該實現有些簡陋,甚至有些缺陷,但對于一般情況應該還是夠用了。在C++11多線程系列的最后會獻上chan的最終版本,敬請期待。
本文將介紹C++11的另一大特性:異步運行(std::async)。async顧名思義是將一個函數A移至另一線程中去運行。A可以是靜態函數、全局函數,甚至類成員函數。在異步運行的過程中,如果A需要向調用者輸出結果怎么辦呢?std::async完美解決了這一問題。在了解async的解決之道前,我們需要一些知識儲備,那就是:std::promise、std::packaged_task和std::future。異步運行涉及的內容較多,我們會分幾節來講。
1. std::promise
std::promise是一個模板類: template<typename R> class promise
。其泛型參數R為std::promise對象保存的值的類型,R可以是void類型。std::promise保存的值可被與之關聯的std::future讀取,讀取操作可以發生在其它線程。std::promise允許move語義(右值構造,右值賦值),但不允許拷貝(拷貝構造、賦值),std::future亦然。std::promise和std::future合作共同實現了多線程間通信。
1.1 設置std::promise的值
通過成員函數set_value可以設置std::promise中保存的值,該值最終會被與之關聯的std::future::get讀取到。需要注意的是:set_value只能被調用一次,多次調用會拋出std::future_error異常。事實上std::promise::set_xxx函數會改變std::promise的狀態為ready,再次調用時發現狀態已要是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會一直阻塞,直到有值到來
std::cout << future->get() << std::endl;
}
int main() {
// promise 相當于生產者
std::promise<std::string> promise;
// future 相當于消費者, 右值構造
std::future<std::string> future = promise.get_future();
// 另一線程中通過future來讀取promise的值
std::thread thread(read, &future);
// 讓read等一會兒:)
std::this_thread::sleep_for(seconds(1));
//
promise.set_value("hello future");
// 等待線程執行完成
thread.join();
return 0;
}
// 控制臺輸: hello future
與std::promise關聯的std::future是通過std::promise::get_future獲取到的,自己構造出來的無效。一個std::promise實例只能與一個std::future關聯共享狀態,當在同一個std::promise上反復調用get_future會拋出future_error異常。
共享狀態。在std::promise構造時,std::promise對象會與共享狀態關聯起來,這個共享狀態可以存儲一個R類型的值或者一個由std::exception派生出來的異常值。通過std::promise::get_future調用獲得的std::future與std::promise共享相同的共享狀態。
1.2 當std::promise不設置值時
如果promise直到銷毀時,都未設置過任何值,則promise會在析構時自動設置為std::future_error,這會造成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不設置任何值
// 則在promise析構時會自動設置為future_error
// 這會造成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 通過std::promise讓std::future拋出異常
通過std::promise::set_exception函數可以設置自定義異常,該異常最終會被傳遞到std::future,并在其get函數中被拋出。
#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轉換一下
promise.set_exception(
std::make_exception_ptr(std::logic_error("caught")));
thread.join();
return 0;
}
// 輸出:logic_error: caught
std::promise雖然支持自定義異常,但它并不直接接受異常對象:
// std::promise::set_exception函數原型
void set_exception(std::exception_ptr p);
自定義異常可以通過位于頭文件exception下的std::make_exception_ptr函數轉化為std::exception_ptr。
1.4 std::promise<void>
通過上面的例子,我們看到std::promise<void>
是合法的。此時std::promise.set_value不接受任何參數,僅用于通知關聯的std::future.get()解除阻塞。
1.5 std::promise所在線程退出時
std::async(異步運行)時,開發人員有時會對std::promise所在線程退出時間比較關注。std::promise支持定制線程退出時的行為:
- std::promise::set_value_at_thread_exit 線程退出時,std::future收到通過該函數設置的值。
- std::promise::set_exception_at_thread_exit 線程退出時,std::future則拋出該函數指定的異常。
關于std::promise就是這些,本文從使用角度介紹了std::promise的能力以及邊界,讀者如果想更深入了解該類,可以直接閱讀一下源碼。
上一篇 C++11多線程-條件變量 |
目錄 | 下一篇 C++11多線程-packaged_task |
---|