std::packaged_task() ---C++17 并发编程
std::packaged_task() —C++17 并發編程
std::packaged_task<>連結了future對象與函數(或可調用對象)。
std::packaged_task<>對象在執行任務時,會調用關聯的函數(或可調用對象),把返回值保存為future的內部數據,并令future準備就緒。它可作為線程池的構件單元,亦可用于其他任務管理方案。
例如,為各個任務分別創建專屬的獨立運行的線程,或者在某個特定的后臺線程上依次執行全部任務。若一項龐雜的操作能分解為多個子任務,則可把它們分別包裝到多個std::packaged_task<>實例之中,再傳遞給任務調度器或線程池。這就隱藏了細節,使任務抽象化,讓調度器得以專注處理std::packaged_task<>實例,無須糾纏于形形色色的任務函數。
std::packaged_task<>是類模板,其模板參數是函數簽名(function signature):譬如,void()表示一個函數,不接收參數,也沒有返回值;又如,int(std::string&,double*)代表某函數,它接收兩個參數并返回int值,其中,第一個參數是非const引用,指向std::string對象,第二個參數是double類型的指針。
假設,我們要構建std::packaged_task<>實例,那么,由于模板參數先行指定了函數簽名,因此傳入的函數(或可調用對象)必須與之相符,即它應接收指定類型的參數,返回值也必須可以轉換為指定類型。
這些類型不必嚴格匹配,若某函數接收int類型參數并返回float值,我們則可以為其構建std::packaged_task<double(double)>的實例,因為對應的類型可進行隱式轉換。
類模板std::packaged_task<>具有成員函數get_future(),它返回std::future<>實例,該future的特化類型取決于函數簽名所指定的返回值。
std::packaged_task<>還具備函數調用操作符,它的參數取決于函數簽名的參數列表。
std::packaged_task對象是可調用對象,我們可以直接調用,還可以將其包裝在std::function對象內,當作線程函數傳遞給std::thread對象,也可以傳遞給需要可調用對象的函數。
若std::packaged_task作為函數對象而被調用,它就會通過函數調用操作符接收參數,并將其進一步傳遞給包裝在內的任務函數,由其異步運行得出結果,并將結果保存到std::future對象內部,再通過get_future()獲取此對象。因此,為了在未來的適當時刻執行某項任務,我們可以將其包裝在std::packaged_task對象內,取得對應的future之后,才把該對象傳遞給其他線程,由它觸發任務執行。
等到需要使用結果時,我們靜候future準備就緒即可。
舉一個不是很恰當的例子:
#pragma once #include <deque> #include <future> #include <mutex> #include <thread> #include <utility> #include <iostream>using namespace std;/** 模擬多線程加載 GUI 界面*/ std::mutex mt; std::deque<std::packaged_task<std::string()>> tasks; std::vector<future<std::string>> futures;void gui_thread() {for (int i = 0; i < 3; ++i){std::packaged_task<std::string()> task;{std::unique_lock<std::mutex> lk(mt);if (tasks.empty())continue;task = std::move(tasks.front());tasks.pop_front();}task();}}template <typename Func> std::future<std::string> post_task_for_gui(Func f) {std::packaged_task<std::string()> task(f);std::future<std::string> res = task.get_future();std::lock_guard<std::mutex> lk(mt);tasks.push_back(std::move(task));return res; }void start_422() {for (int i = 0; i < 3; ++i){futures.push_back(post_task_for_gui([=]()-> std::string{std::this_thread::sleep_for(3s);return std::format("第 {} 任務執行完畢", i);}));}cout << "任務提交完成" << endl;std::thread gui_background_thread(gui_thread);if (gui_background_thread.joinable())gui_background_thread.detach(); //后臺執行for (auto& value : futures){cout << value.get() << endl;} }本例采用std::packaged_task<void()>表示任務,包裝某個函數(或可調用對象),它不接收參數,返回void(倘若真正的任務函數返回任何其他類型的值,則會被丟棄)。
這里,我們采用最簡單的任務舉例,但前文已提過,std::packaged_task也能用于更復雜的情況。針對不同的任務函數,std::packaged_task的函數調用操作符須就此修改參數,保存于相關的future實例內的返回值類型也須變動,而我們只要通過模板參數,指定對應任務的函數簽名即可。
我們可輕松擴展上例,改動那些只準許在GUI線程上運行的任務,令其接收參數,并憑借std::future返回結果,std::future不再局限于充當指標,示意任務是否完成。
總結
以上是生活随笔為你收集整理的std::packaged_task() ---C++17 并发编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试中有哪些经典的数据库问题?
- 下一篇: Cocos2D场景编辑器