1. 程式人生 > 其它 >c++ 併發多執行緒學習筆記

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;
}

img

永遠都會先打印出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() 否則程式會報異常

小結:

我們學習這些東西的目的並不是,要把他們都用到實際開發中。

相反,如果我們能夠用最少的東西寫出一個穩定的,高效的多執行緒程式,更值得讚賞。

我們為了成長必須閱讀一些高手寫的程式碼,從而實現自己程式碼的積累;