C++11 std::packaged_task的妙用
std::packaged_task簡介
std::packaged_task是C++11開始加入到STL(Standard Template Libarary, 標準模板庫)的併發編程工具,位於頭文件 <future>中, 使用時只需要包含該頭文件即可
使用場景
假設我們的可執行程序有如下要求:
某些第三方提供的API未使用加鎖保護機制,但這些API調用卻不允許在不同線程裏併發的調用, 比如涉及到單端口通信的API以及操作UI控件的API, 同一時間段內僅能進行一次調用, 這種情況下, 就必須要保證該系列的API的調用必須在單線程中調用.
具體怎麼做呢?
應對方案
方案1: 使用任務隊列
開啓獨立線程, 將所有涉及到相關API調用的操作放入某個串行的任務隊列中, 然後在開啓的獨立線程中循環調用
方案2: 使用事件循環
在某些流程中, 可能會使用到事件循環, 並提供了相應的事件觸發接口將對應的事件加入到主事件循環隊列中, 由事件響應者進行任務調用
方案分析
對於方案1, 我們的工作量相對比較少, 創建任務隊列, 創建線程並循環執行隊列中的任務
對於方案2, 我們的步驟相對繁瑣一些, 針對該系列任務定製事件, 定義事件響應回調
綜合分析
以上方案均能保證我們的API可以合理的放到某單個線程(主線程或新線程)中去, 但是一般情況下, 通過這種方式, 我們只能將任務放到單線程去執行, 而如果相獲取結果的話, 在C++11之前還是相當麻煩的.不過自從C++11, 我們有了一個很厲害的神兵利器用來解決我們的這一需求:
發起異步任務, 等待執行結果
具體實現
-
將所有相關的API調用任務做一層通用的包裝, 比如放到std::function<void()>中
-
使用現有的事件邏輯或者開啓新的線程去執行對應包裝的任務
-
使用std::packaged_task將任務和任務參數進行任務包裝, 可以設置不同的返回值, 根據具體api的情況而定, 比如:
int do_something(int arg), 可以包裝爲std::packaged_task<int()>)
-
獲取std::packaged_task的future, 用來獲取包裝起來的任務中的返回值
auto future = task.get_future();
- 將 包裝好的task通過已有的事件觸發接口或者自定義的推送到任務隊列的接口將task放入std::function中推送到任務隊列中
- 調用future.wait() 或者future.get()等方式來等待返回值, 如果不惜要返回值, 也可以直接wait()即可, 這樣既保證了api的調用一定是在單線程, 並且發起調用的線程中還可以使用同步的方式等待結果
具體示例代碼
#include <queue>
#include <future>
#include <functional>
#include <iostream>
#include <thread>
int main(int, char **)
{
int ticks = 0;
/*訪問隊列時的保護鎖*/
std::mutex task_lock;
/*這是全局的任務隊列*/
std::queue<std::function<void()>> task_list;
/*後臺發起任務的調用*/
std::thread background([&]{
while(ticks < 100) {
auto task = std::make_shared<std::packaged_task<bool()>>(std::bind([=] (int ticks) -> bool {
std::cout << "[Main] execute task ticks: " << ticks << std::endl;
return true;
}, ticks));
auto future = task->get_future();
std::cout << "[Background] get task future" << std::endl;
{
std::lock_guard<std::mutex> lock(task_lock);
std::cout << "[Background] push task, ticks: " << ticks << std::endl;
task_list.push([=] {
if(task) (*task)();
});
}
std::this_thread::sleep_for(std::chrono::seconds(1));
ticks++;
}
});
/*主線程循環執行隊列任務*/
std::queue<std::function<void()>> tasks;
while (ticks < 100) {
if(task_list.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
} else {
{
std::lock_guard<std::mutex> lock(task_lock);
tasks.swap(task_list);
}
while(!tasks.empty()) {
auto task = tasks.front();
if(task) {
task ();
}
tasks.pop();
}
}
}
background.join();
return 0;
}