您的位置:首页 > 编程语言 > C语言/C++

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

2016-11-15 18:45 309 查看
今天在偶然在找尋有關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<
4000
span style="color:#406080;">;		//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_task
t=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							//宣告臨界區mutex

void 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 mu
std::condition_variable cond;							//宣告condition_variable

void thread_1()
{
std::packaged_task<int()>t;
{
std::unique_lock<std::mutex> locker(mu);			//使用condition_variable,lock的方法必須為unique_lock
cond.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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ stl future