c++ 併發多執行緒學習筆記
技術標籤:筆記
1、執行緒啟動/結束
1.1、thread,join(),detach(),joinable()基本用法
#include<iostream>
#include<thread>
void myPrint()
{
std::cout<< "我的執行緒開始執行"<<std::endl;
//。。。。。。
//...........
std::cout << "我的執行緒結束執行" << std::endl;
}
int main(void)
{
std::thread myThread (myPrint);
//myThread.detach();
myThread.join();
if (myThread.joinable())
{
std::cout << "可以呼叫" << std::endl;
}
else
{
std::cout << "不能呼叫" << std::endl;
}
std::cout << "abc" << std::endl;
}
1.2、建立執行緒的方法
1.2.1、 過載了函式呼叫運算子的類物件
#include<iostream>
#include<thread>
#include<string>
class Ta
{
public:a
//void operator()(int a)
void operator()()//引數可有可無,注意呼叫格式
{
std::cout << "start" << std::endl;
std::cout << "end" << std::endl;
}
};
int main()
{
Ta ta;
//std::thread myThread(ta,1);
std::thread myThread(ta);
myThread.join();
if (myThread.joinable())
{
std::cout << "可以呼叫" << std::endl;
}
else
{
std::cout << "不能呼叫" << std::endl;
}
std::cout << "abc" << std::endl;
}
1.2.2、lambda表示式建立執行緒
#include<iostream>
#include<thread>
#include<string>
int main()
{
auto lambdaThread = [] {
std::cout << "我的執行緒開始執行了" << std::endl;
//-------------
//-------------
std::cout << "我的執行緒開始執行了" << std::endl;
};
std::thread myThread(lambdaThread);
myThread.join();
if (myThread.joinable())
{
std::cout << "可以呼叫" << std::endl;
}
else
{
std::cout << "不能呼叫" << std::endl;
}
std::cout << "abc" << std::endl;
}
1.2.3、把某個類中的某個函式作為執行緒的入口地址
注意:兩個輸出是沒用順序,隨機的
class Data_
{
public:
void GetMsg() {
std::cout << "first";
}
void SaveMsh() {
std::cout << "second";
}
};
int main()
{
Data_ s;
//第一個&意思是取址,第二個&意思是引用,相當於std::ref(s)
//thread oneobj(&Data_::SaveMsh,s)傳值也是可以的
//在其他的建構函式中&obj是不會代表引用的,會被當成取地址
std::thread myThread1(&Data_::SaveMsh,&s);
std::thread myThread2(&Data_::GetMsg, &s);
myThread1.join();
myThread2.join();
std::cout << "abc" << std::endl;
}
2、detach()的坑
2.1、傳遞臨時物件作為執行緒引數
2.1.1、要避免的陷阱1:
#include <iostream>
#include <thread>
using namespace std;
void myPrint(const int& i, char* pmybuf)
{
//如果執行緒從主執行緒detach了
//i不是mvar真正的引用,實際上值傳遞,即使主執行緒執行完畢了,子執行緒用i仍然是安全的,但仍不推薦傳遞引用
//推薦改為const int i
cout << i << endl;
//pmybuf還是指向原來的字串,所以這麼寫是不安全的
cout << pmybuf << endl;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test";
thread myThread(myPrint, mvar, mybuf);//第一個引數是函式名,後兩個引數是函式的引數
//myThread.join();
myThread.detach();
cout << "Hello World!" << endl;
}
2.1.2要避免的陷阱2:
#include <iostream>
#include <thread>
#include <string>
using namespace std;
void myPrint(const int i, const string& pmybuf)
{
cout << i << endl;
cout << pmybuf << endl;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test";
//如果detach了,這樣仍然是不安全的
//因為存在主執行緒執行完了,mybuf被回收了,系統採用mybuf隱式型別轉換成string
//推薦先建立一個臨時物件thread myThread(myPrint, mvar, string(mybuf));就絕對安全了。。。。
thread myThread(myPrint, mvar, mybuf);
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
}
總結:
- 如果傳遞int這種簡單型別,推薦使用值傳遞,不要用引用
- 如果傳遞類物件,避免使用隱式型別轉換,全部都是建立執行緒這一行就創建出臨時物件,然後在函式引數裡,用引用來接,否則還會創建出一個物件
- 終極結論:建議不使用detach
2.2、執行緒id
- id是個數字,每個執行緒(不管是主執行緒還是子執行緒)實際上都對應著一個數字,而且每個執行緒對應的這個數字都不一樣
- 執行緒id可以用C++標準庫裡的函式來獲取。std::this_thread::get_id()來獲取
2.3、 傳遞類物件、智慧指標作為執行緒引數
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
mutable int m_i; //m_i即使實在const中也可以被修改
A(int i) :m_i(i) {}
};
void myPrint(const A& pmybuf)
//使用引用的話必須使用const,語法問題
{
pmybuf.m_i = 199;
cout << "子執行緒myPrint的引數地址是" << &pmybuf << "thread = " << std::this_thread::get_id() << endl;
}
int main()
{
A myObj(10);
//myPrint(const A& pmybuf)中引用不能去掉,如果去掉會多建立一個物件
//const也不能去掉,去掉會出錯
//即使是傳遞的const引用,但在子執行緒中還是會呼叫拷貝建構函式構造一個新的物件,
//所以在子執行緒中修改m_i的值不會影響到主執行緒
//如果希望子執行緒中修改m_i的值影響到主執行緒,可以用thread myThread(myPrint, std::ref(myObj));
//這樣const就是真的引用了,myPrint定義中的const就可以去掉了,類A定義中的mutable也可以去掉了
thread myThread(myPrint, myObj);
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
}
#include <iostream>
#include <thread>
#include <memory>
using namespace std;
void myPrint(unique_ptr<int> ptn)
{
cout << "thread = " << std::this_thread::get_id() << endl;
}
int main()
{
unique_ptr<int> up(new int(10));
//獨佔式指標只能通過std::move()才可以傳遞給另一個指標
//傳遞後up就指向空,新的ptn指向原來的記憶體
//所以這時就不能用detach了,因為如果主執行緒先執行完,ptn指向的物件就被釋放了
thread myThread(myPrint, std::move(up));
myThread.join();
//myThread.detach();
return 0;
}
3、建立多個執行緒、資料共享問題分析、案例程式碼
3.1、建立和等待多個執行緒
void TextThread()
{
cout << "我是執行緒" << this_thread::get_id() << endl;
/* … */
cout << "執行緒" << this_thread::get_id() << "執行結束" << endl;
}
//main函式裡 vector threadagg;
for (int i = 0; i < 10; ++i)
{
threadagg.push_back(thread(TextThread));
}
for (int i = 0; i < 10; ++i)
{
threadagg[i].join();
}
- 把thread物件放入到容器中管理,看起來像個thread物件陣列,對一次建立大量的執行緒並對大量執行緒進行管理有好處
- 多個執行緒執行順序是亂的,跟作業系統內部對執行緒的執行排程機制有關
3.2、資料共享問題
3.2.1、只讀的資料
- 是安全穩定的
3.2.2、 有讀有寫
- 若不加處理,就會出錯
- 最簡單的防止崩潰方法:讀的時候不能寫,寫的時候不能讀。
- 寫的動作分10小步,由於任務切換,導致各種詭異的事情發生(最可能的還是崩潰)
4、 互斥量概念、用法、死鎖演示及解決詳解
4.1、 互斥量(mutex)的基本概念
- 互斥量就是個類物件,可以理解為一把鎖,多個執行緒嘗試用lock()成員函式來加鎖,只有一個執行緒能鎖定成功,如果沒有鎖成功,那麼流程將卡在lock()這裡不斷嘗試去鎖定。
- 互斥量使用要小心,保護資料不多也不少,少了達不到效果,多了影響效率。
4.2、 互斥量的用法
包含#include 標頭檔案
4.2.1、 lock(),unlock()
- 步驟:1.lock(),2.操作共享資料,3.unlock()。
- lock()和unlock()要成對使用
4.2.2 、lock_guard類模板
- lock_guard sbguard(myMutex);取代lock()和unlock()
- lock_guard建構函式執行了mutex::lock();在作用域結束時,呼叫解構函式,執行mutex::unlock()
4.3、死鎖
4.3.1、 死鎖演示
死鎖至少有兩個互斥量mutex1,mutex2。
- a.執行緒A執行時,這個執行緒先鎖mutex1,並且鎖成功了,然後去鎖mutex2的時候,出現了上下文切換。
- b.執行緒B執行,這個執行緒先鎖mutex2,因為mutex2沒有被鎖,即mutex2可以被鎖成功,然後執行緒B要去鎖mutex1.
- c.此時,死鎖產生了,A鎖著mutex1,需要鎖mutex2,B鎖著mutex2,需要鎖mutex1,兩個執行緒沒辦法繼續執行下去。。。
4.3.2、 死鎖的一般解決方案:
只要保證多個互斥量上鎖的順序一樣就不會造成死鎖。
4.3.3 、std::lock()函式模板
- std::lock(mutex1,mutex2……); 一次鎖定多個互斥量(一般這種情況很少),用於處理多個互斥量。
- 如果互斥量中一個沒鎖住,它就等著,等所有互斥量都鎖住,才能繼續執行。如果有一個沒鎖住,就會把已經鎖住的釋放掉(要麼互斥量都鎖住,要麼都沒鎖住,防止死鎖)
4.3.4 、std::lock_guard的std::adopt_lock引數
-
std::lock_guardstd::mutex my_guard(my_mutex,std::adopt_lock);
加入adopt_lock後,在呼叫lock_guard的建構函式時,不再進行lock(); -
adopt_guard為結構體物件,起一個標記作用,表示這個互斥量已經lock(),不需要在lock()。
5、unique_lock(類模板)
5.1、unique_lock取代lock_guard
unique_lock比lock_guard靈活很多(多出來很多用法),效率差一點。
unique_lock myUniLock(myMutex);
5.2、unique_lock的第二個引數
5.2.1、 std::adopt_lock:
- 表示這個互斥量已經被lock(),即不需要在建構函式中lock這個互斥量了。
- 前提:必須提前lock
- lock_guard中也可以用這個引數
5.2.2 、std::try_to_lock:
- 嘗試用mutx的lock()去鎖定這個mutex,但如果沒有鎖定成功,會立即返回,不會阻塞在那裡;
- 使用try_to_lock的原因是防止其他的執行緒鎖定mutex太長時間,導致本執行緒一直阻塞在lock這個地方
- 前提:不能提前lock();
- owns_locks()方法判斷是否拿到鎖,如拿到返回true
5.2.3、 std::defer_lock:
- 如果沒有第二個引數就對mutex進行加鎖,加上defer_lock是始化了一個沒有加鎖的mutex
- 不給它加鎖的目的是以後可以呼叫unique_lock的一些方法
- 前提:不能提前lock
5.3、unique_lock的成員函式(前三個與std::defer_lock聯合使用)
5.3.1、 lock():加鎖
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
12
不用自己unlock();
5.3.2 unlock():解鎖
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
//處理一些共享程式碼
myUniLock.unlock();
//處理一些非共享程式碼
myUniLock.lock();
//處理一些共享程式碼
1234567
因為一些非共享程式碼要處理,可以暫時先unlock(),用其他執行緒把它們處理了,處理完後再lock()。
5.3.3 try_lock():嘗試給互斥量加鎖
如果拿不到鎖,返回false,否則返回true。
5.3.4 release():
- unique_lock
myUniLock(myMutex);相當於把myMutex和myUniLock繫結在了一起,release()就是解除繫結,返回它所管理的mutex物件的指標,並釋放所有權 - mutex* ptx =
myUniLock.release();所有權由ptx接管,如果原來mutex物件處理加鎖狀態,就需要ptx在以後進行解鎖了。
lock的程式碼段越少,執行越快,整個程式的執行效率越高。
a.鎖住的程式碼少,叫做粒度細,執行效率高;
b.鎖住的程式碼多,叫做粒度粗,執行效率低;
5.4.unique_lock所有權的傳遞
unique_lock myUniLock(myMutex);把myMutex和myUniLock繫結在了一起,也就是myUniLock擁有myMutex的所有權
1. 使用move轉移
- myUniLock擁有myMutex的所有權,myUniLock可以把自己對myMutex的所有權轉移,但是不能複製。
- unique_lock myUniLock2(std::move(myUniLock));
現在myUniLock2擁有myMutex的所有權。
2. 在函式中return一個臨時變數,即可以實現轉移
unique_lock<mutex> aFunction()
{
unique_lock<mutex> myUniLock(myMutex);
//移動建構函式那裡講從函式返回一個區域性的unique_lock物件是可以的
//返回這種區域性物件會導致系統生成臨時的unique_lock物件,並呼叫unique_lock的移動建構函式
return myUniLock;
}
6、單例設計模式共享資料分析、解決,call_once
6.1、設計模式
- 程式靈活,維護起來可能方便,用設計模式理念寫出來的程式碼很晦澀,但是別人接管、閱讀程式碼都會很痛苦
- 老外應付特別大的專案時,把專案的開發經驗、模組劃分經驗,總結整理成設計模式
- 中國零幾年設計模式剛開始火時,總喜歡拿一個設計模式往上套,導致一個小小的專案總要加幾個設計模式,本末倒置
- 設計模式有其獨特的優點,要活學活用,不要深陷其中,生搬硬套
6.2、單例設計模式
整個專案中,有某個或者某些特殊的類,只能建立一個屬於該類的物件。
單例類:只能生成一個物件。
6.3、單例設計模式共享資料分析、解決
面臨問題:需要在自己建立的執行緒中來建立單例類的物件,這種執行緒可能不止一個。我們可能面臨GetInstance()這種成員函式需要互斥。
可以在加鎖前判斷instance是否為空,否則每次呼叫Singelton::getInstance()都要加鎖,十分影響效率。 解釋看註釋。
#include <iostream>
#include <mutex>
using namespace std;
mutex myMutex;
//懶漢模式
class Singelton
{
public:
static Singelton* getInstance() {
//提高效率
//(a)如果if(instance!=NULL),代表肯定被new過
//(b)如果if(instance==NULL),不代表instance沒被new過。可能此時剛準備new的時候,已近被別的執行緒new過了。
//雙重鎖定 提高效率
if (instance == NULL) {
lock_guard<mutex> myLockGua(myMutex);
if (instance == NULL) {
instance = new Singelton;
}
}
return instance;
}
private:
Singelton() {}
static Singelton* instance;
};
Singelton* Singelton::instance = NULL;
//餓漢模式
class Singelton2 {
public:
static Singelton2* getInstance() {
return instance;
}
private:
Singelton2() {}
static Singelton2* instance;
};
Singelton2* Singelton2::instance = new Singelton2;
int main(void)
{
Singelton* singer = Singelton::getInstance();
Singelton* singer2 = Singelton::getInstance();
if (singer == singer2)
cout << "二者是同一個例項" << endl;
else
cout << "二者不是同一個例項" << endl;
cout << "---------- 以下 是 餓漢式 ------------" << endl;
Singelton2* singer3 = Singelton2::getInstance();
Singelton2* singer4 = Singelton2::getInstance();
if (singer3 == singer4)
cout << "二者是同一個例項" << endl;
else
cout << "二者不是同一個例項" << endl;
return 0;
}
如果覺得在單例模式new了一個物件,而沒有自己delete掉,這樣不合理。可以增加一個類中類CGarhuishou,new一個單例類時建立一個靜態的CGarhuishou物件,這樣在程式結束時會呼叫CGarhuishou的解構函式,釋放掉new出來的單例物件。
class Singelton
{
public:
static Singelton * getInstance() {
if (instance == NULL) {
static CGarhuishou huishou;
instance = new Singelton;
}
return instance;
}
class CGarhuishou {
public:
~CGarhuishou()
{
if (Singelton::instance)
{
delete Singelton::instance;
Singelton::instance = NULL;
}
}
};
private:
Singelton() {}
static Singelton *instance;
};
Singelton * Singelton::instance = NULL;
6.4、std::call_once()
函式模板,該函式的第一個引數為標記,第二個引數是一個函式名(如a())。
功能:能夠保證函式a()只被呼叫一次。具備互斥量的能力,而且比互斥量消耗的資源更少,更高效。
call_once()需要與一個標記結合使用,這個標記為std::once_flag;
其實once_flag是一個結構,call_once()就是通過標記來決定函式是否執行,
呼叫成功後,就把標記設定為一種已呼叫狀態。
再次呼叫的時候,檢查到此種狀態就不會執行,
多個執行緒同時執行時,一個執行緒會等待另一個執行緒先執行。
once_flag g_flag;
class Singelton
{
public:
static void CreateInstance()//call_once保證其只被呼叫一次
{
instance = new Singelton;
}
//兩個執行緒同時執行到這裡,其中一個執行緒要等另外一個執行緒執行完畢
static Singelton * getInstance() {
call_once(g_flag, CreateInstance);
return instance;
}
private:
Singelton() {}
static Singelton *instance;
};
Singelton * Singelton::instance = NULL;
7、condition_variable、wait、notify_one、notify_all
7.1、條件變數condition_variable、wait、notify_one、notify_all
std::condition_variable實際上是一個類,是一個和條件相關的類,說白了就是等待一個條件達成。
std::mutex mymutex1;
std::unique_lock<std::mutex> sbguard1(mymutex1);
std::condition_variable condition;
condition.wait(sbguard1, [this] {if (!msgRecvQueue.empty())
return true;
return false;
});
condition.wait(sbguard1);
wait()用來等一個東西
如果第二個引數的lambda表示式返回值是false,那麼wait()將解鎖互斥量,並阻塞到本行
如果第二個引數的lambda表示式返回值是true,那麼wait()直接返回並繼續執行。
阻塞到什麼時候為止呢?阻塞到其他某個執行緒呼叫notify_one()成員函式為止;
如果沒有第二個引數,那麼效果跟第二個引數lambda表示式返回false效果一樣
wait()將解鎖互斥量,並阻塞到本行,阻塞到其他某個執行緒呼叫notify_one()成員函式為止。
當其他執行緒用notify_one()將本執行緒wait()喚醒後,這個wait恢復後
1、wait()不斷嘗試獲取互斥量鎖,如果獲取不到那麼流程就卡在wait()這裡等待獲取,如果獲取到了,那麼wait()就繼續執行,獲取到了鎖
2.1、如果wait有第二個引數就判斷這個lambda表示式。
- a)如果表示式為false,那wait又對互斥量解鎖,然後又休眠,等待再次被notify_one()喚醒
- b)如果lambda表示式為true,則wait返回,流程可以繼續執行(此時互斥量已被鎖住)。
2.2、如果wait沒有第二個引數,則wait返回,流程走下去。
流程只要走到了wait()下面則互斥量一定被鎖住了。
#include <thread>
#include <iostream>
#include <list>
#include <mutex>
using namespace std;
class A {
public:
void inMsgRecvQueue() {
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue插入一個元素" << i << endl;
std::unique_lock<std::mutex> sbguard1(mymutex1);
msgRecvQueue.push_back(i);
//嘗試把wait()執行緒喚醒,執行完這行,
//那麼outMsgRecvQueue()裡的wait就會被喚醒
//只有當另外一個執行緒正在執行wait()時notify_one()才會起效,否則沒有作用
condition.notify_one();
}
}
void outMsgRecvQueue() {
int command = 0;
while (true) {
std::unique_lock<std::mutex> sbguard2(mymutex1);
// wait()用來等一個東西
// 如果第二個引數的lambda表示式返回值是false,那麼wait()將解鎖互斥量,並阻塞到本行
// 阻塞到什麼時候為止呢?阻塞到其他某個執行緒呼叫notify_one()成員函式為止;
//當 wait() 被 notify_one() 啟用時,會先執行它的 條件判斷表示式 是否為 true,
//如果為true才會繼續往下執行
condition.wait(sbguard2, [this] {
if (!msgRecvQueue.empty())
return true;
return false;});
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
//因為unique_lock的靈活性,我們可以隨時unlock,以免鎖住太長時間
sbguard2.unlock();
cout << "outMsgRecvQueue()執行,取出第一個元素" << endl;
}
}
private:
std::list<int> msgRecvQueue;
std::mutex mymutex1;
std::condition_variable condition;
};
int main() {
A myobja;
std::thread myoutobj(&A::outMsgRecvQueue, &myobja);
std::thread myinobj(&A::inMsgRecvQueue, &myobja);
myinobj.join();
myoutobj.join();
}
7.2、深入思考
上面的程式碼可能導致出現一種情況:
因為outMsgRecvQueue()與inMsgRecvQueue()並不是一對一執行的,所以當程式迴圈執行很多次以後,可能在msgRecvQueue 中已經有了很多訊息,但是,outMsgRecvQueue還是被喚醒一次只處理一條資料。這時可以考慮把outMsgRecvQueue多執行幾次,或者對inMsgRecvQueue進行限流。
7.3、notify_all()
notify_one()(隨機喚醒一個等待的執行緒)和notify_all()(喚醒所有等待的執行緒)
notify_one():通知一個執行緒的wait()
notify_all():通知所有執行緒的wait()
8、async、future、packaged_task、promise
8.1、std::async、std::future建立後臺任務並返回值
std::async是一個函式模板,用來啟動一個非同步任務,啟動起來一個非同步任務之後,它返回一個std::future物件,這個物件是個類模板。
什麼叫“啟動一個非同步任務”?就是自動建立一個執行緒,並開始 執行對應的執行緒入口函式,它返回一個std::future物件,這個std::future物件中就含有執行緒入口函式所返回的結果,我們可以通過呼叫future物件的成員函式get()來獲取結果。
“future”將來的意思,也有人稱呼std::future提供了一種訪問非同步操作結果的機制,就是說這個結果你可能沒辦法馬上拿到,但是在不久的將來,這個執行緒執行完畢的時候,你就能夠拿到結果了,所以,大家這麼理解:future中儲存著一個值,這個值是在將來的某個時刻能夠拿到。
std::future物件的get()成員函式會等待執行緒執行結束並返回結果,拿不到結果它就會一直等待,感覺有點像join()但是,它是可以獲取結果的。
std::future物件的wait()成員函式,用於等待執行緒返回,本身並不返回結果,這個效果和 std::thread 的join()更像。
#include <iostream>
#include <future>
using namespace std;
class A {
public:
int mythread(int mypar) {
cout << mypar << endl;
return mypar;
}
};
int mythread() {
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
//延遲5秒鐘
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
A a;
int tmp = 12;
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(mythread);
cout << "continue........" << endl;
cout << result1.get() << endl; //卡在這裡等待mythread()執行完畢,拿到結果
//類成員函式
std::future<int> result2 = std::async(&A::mythread, &a, tmp); //第二個引數是物件引用才能保證執行緒裡執行的是同一個物件
cout << result2.get() << endl;
//或者result2.wait();
cout << "good luck" << endl;
return 0;
}
8.2、std::launch(列舉型別)
我們通過向std::async()傳遞一個引數,改引數是std::launch型別(列舉型別),來達到一些特殊的目的:
8.2.1、std::lunch::deferred:
(defer推遲,延期)表示執行緒入口函式的呼叫會被延遲,一直到std::future的wait()或者get()函式被呼叫時(由主執行緒呼叫)才會執行;如果wait()或者get()沒有被呼叫,則不會執行。
注意:實際上根本就沒有建立新執行緒。std::lunch::deferred意思時延遲呼叫,並沒有建立新執行緒,是在主執行緒中呼叫的執行緒入口函式。
#include <iostream>
#include <future>
using namespace std;
int mythread() {
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::deferred ,mythread);
cout << "continue........" << endl;
cout << result1.get() << endl; //卡在這裡等待mythread()執行完畢,拿到結果
cout << "good luck" << endl;
return 0;
}
永遠都會先打印出continue…,然後才會打印出mythread() start和mythread() end等資訊。
8.2.2、std::launch::async
在呼叫async函式的時候就開始建立新執行緒。
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::async ,mythread);
cout << "continue........" << endl;
cout << result1.get() << endl;
cout << "good luck" << endl;
return 0;
}
8.3、std::packaged_task:打包任務,把任務包裝起來
類模板,它的模板引數是各種可呼叫物件,通過packaged_task把各種可呼叫物件包裝起來,方便將來作為執行緒入口函式來呼叫。
#include <thread>
#include <iostream>
#include <future>
using namespace std;
int mythread(int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
//我們把函式mythread通過packaged_task包裝起來
//引數是一個int,返回值型別是int
std::packaged_task<int(int)> mypt(mythread);
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
//std::future物件裡包含有執行緒入口函式的返回結果,這裡result儲存mythread返回的結果。
cout << result.get() << endl;
return 0;
}
可呼叫物件可由函式換成lambda表示式
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
//std::future物件裡包含有執行緒入口函式的返回結果,這裡result儲存mythread返回的結果。
cout << result.get() << endl;
cout << "good luck" << endl;
return 0;
}
packaged_task包裝起來的可呼叫物件還可以直接呼叫,從這個角度來講,packaged_task物件也是一個可呼叫物件
lambda的直接呼叫
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
mypt(1);
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
}
std::promise,類模板
我們能夠在某個執行緒中給它賦值,然後我們可以在其他執行緒中,把這個值取出來
#include <thread>
#include <iostream>
#include <future>
using namespace std;
void mythread(std::promise<int> &tmp, int clac) {
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
int result = clac;
tmp.set_value(result); //結果儲存到了tmp這個物件中
return;
}
vector<std::packaged_task<int(int)>> task_vec;
int main() {
std::promise<int> myprom;
std::thread t1(mythread, std::ref(myprom), 180);
t1.join(); //在這裡執行緒已經執行完了
std::future<int> fu1 = myprom.get_future(); //promise和future繫結,用於獲取執行緒返回值
auto result = fu1.get();
cout << "result = " << result << endl;
}
總結:通過promise儲存一個值,在將來某個時刻我們通過吧一個future繫結到這個promise上,來得到繫結的值
注意:使用thread時,必須 join() 或者 detach() 否則程式會報異常
小結:
我們學習這些東西的目的並不是,要把他們都用到實際開發中。
相反,如果我們能夠用最少的東西寫出一個穩定的,高效的多執行緒程式,更值得讚賞。
我們為了成長必須閱讀一些高手寫的程式碼,從而實現自己程式碼的積累;