C++並發練習 筆記(一)STL<future>中的packaged_task

来源:互联网 发布:成都知美术馆怎么去 编辑:程序博客网 时间:2024/06/08 13:23

今天在偶然在找尋有關OpenCV的演算法實作方法時,看到了大牛們使用了packaged_task的這樣一個函數。

自己是沒見過的,當然就趕快操起google搜尋及手邊資料進行搜查。

並自己在網絡上找了一些視頻資源加以學習,並在此留下筆記,用來強化記憶及供人及自己日後參考。

推薦學習視頻連結:youtube:C++ Threading #9: packaged_task

Bo qian大神的一系列視頻講的真是不錯,每次都能從他的代碼中學到許多東西,因此真心推薦。


進入正文:

首先先附上來自cppreference.com的C++字典說明。來源連結

packaged_task被定義在頭文件<future>當中,而看到<future>這個頭文件,應該很多同學都跟我一樣心裡有了個底,這是個將會是個異步函數的調用

,就跟我們想的一樣,它確實就是如此。

這個模板函數能封裝任何callable的物件,包括function,lambda,bind expresion,function object,)而被包裝在其中的函數

將會被以異步的方式進行調用。

這是它的建構子。

template< class > class packaged_task; //not defined
(1)(since C++11)template< class R, class ...Args > 
class packaged_task<R(Args...)>;
(2)(since C++11)

恩..確實有些難懂,不如先來看一下大至的使用方法

int Factorial(int N)//被包裝函數的宣告及實現{int res=1;for(int i=N;i>1;i--)res*=i;std::cout<<"Result is"<<res<<std::endl;return res;}std::deque<std::packaged_task<int()>> task_q;//packeged_task的容器int main(){std::packaged_task<int()> t(bind(Fatorial,6));  //包裝函數task_q.push_back(t);//將被包裝的函數丟入容器中return 0;}

在main函數中 std::packaged_task<>模板函數所包含的的是int();

其中int 代表的是被包裝函數的返回值,()不加入任何類型代表不帶任何參數,假如有參數則可以寫成如下

std::packaged_task<int (int ,string&)>//接受兩個參數 int,string&,並返回int

而在本範例中,要被包裝的函數是 int Factorial (int),理應當寫成

std::packaged_task<int (int)>//接受一個參數 int,並返回int
但是卻寫成了

std::packaged_task<int ()>//不接受參數,並返回int
仔細看一下後面,原來被包裝的函數與參數綁定了,bind(Factorial,6),使得原本的模版函數不能在接受更多參數

故而最後寫成

std::packaged_task<int()> t(bind(Factorial,6))//不接受參數,並返回int,同時包裝了bind function obj (Factorial,6)
這是在應用上須要相當注意的地方。


t現在代表了一個異步調用,如果在之後想要獲取它的值,可以直接寫以下代碼來獲取它的值

std::future<int> nAsyncResult=t.get_future()
但是你看到接下來的代碼

task_q.push_back(t);
我們將它放入了一個雙向對列之中,這是為了接下來比較有挑戰性的法,同時使用thread和異步調用。

來看一下剛剛範例的進階版,斜體加粗的部分代表跟剛剛比較多出來的部分。

int Factorial(int N){int res=1;for(int i=N;i>1;i--)res*=i;cout<<"Result is"<<res<<endl;return res;}std::deque<std::packaged_task<int()>> task_q;void thread_1()//線程函數的實作{std::packaged_task<int()>t;//宣到一個封裝int()函數類型的package_taskt=std::move(task_q.front());//t=容器最上方的值,由於容器中的右值不再使用,所以利用move語句,將右值直接給t,而不是拷貝一份。t();//執行t}int main(){std::thread t1(thread_1);std::packaged_task<int()> t(bind(Fatorial,6));task_q.push_back(t);t1.join();//確保主線程等待執行緒結束return 0;}

這樣代碼就成了異步的生產與消費者線程模型。

但這樣的代碼仍然沒有考慮到資源競爭,以及線程同步的問題,讓我們繼續完善它。(使用Mutex 跟lock_guard)

int Factorial(int N){int res=1;for(int i=N;i>1;i--)res*=i;cout<<"Result is"<<res<<endl;return res;}std::deque<std::packaged_task<int()>> task_q;std::mutex mu//宣告臨界區mutexvoid thread_1(){std::packaged_task<int()>t;{std::lock_guard<std::mutex> locker(mu);//lock guard必須與生產者(主線程使用同一個臨界區t=std::move(task_q.front());task_q.pop_front();}t();}int main(){std::thread t1(thread_1);std::packaged_task<int()> t(bind(Fatorial,6));std::future<int> nFuture=t.get_future();//在將來以異步的方式獲取該值{std::lock_guard<std::mutex> locker(mu);//lock guard必須與消費者(線程1)使用同一個臨界區task_q.push_back(std::move(t));}t1.join();return 0;}

但這樣仍無法保證,生產者將工作塞入容器前,消費者不能先將物品取出來,必須確認容器不為空時才繼續執行動作,因此我們再導入

condition_variable

int Factorial(int N){int res=1;for(int i=N;i>1;i--)res*=i;cout<<"Result is"<<res<<endl;return res;}std::deque<std::packaged_task<int()>> task_q;std::mutex mustd::condition_variable cond;//宣告condition_variablevoid thread_1(){std::packaged_task<int()>t;{std::unique_lock<std::mutex> locker(mu);//使用condition_variable,lock的方法必須為unique_lockcond.wait(locker,[](){return !task_q.empty();});//使用lambda function來指示當容器為空,則不執行任何動作。t=std::move(task_q.front());task_q.pop_front();}t();}int main(){std::thread t1(thread_1);std::packaged_task<int()> t(bind(Fatorial,6));std::future<int> nFuture=t.get_future();{std::lock_guard<std::mutex> locker(mu);task_q.push_back(std::move(t));}cond.notify_one();//通知另一個在等待的線程可以繼續執行,如果多線程使用notify_all()cout<<fu.get();//獲取異步調用的值,並打印t1.join();return 0;}






0 0
原创粉丝点击