C++詳細(xì)分析線程間的同步通信
1、多線程編程兩個(gè)問題
1.1、線程間的互斥
競(jìng)態(tài)條件: 多線程執(zhí)行的結(jié)果是一致的,不會(huì)隨著CPU對(duì)線程不同的調(diào)用順序,而產(chǎn)生不同的運(yùn)行結(jié)果。
發(fā)生競(jìng)態(tài)條件的代碼段,稱為臨界區(qū)代碼段(只有一個(gè)線程可以進(jìn)來(lái)),保證臨界區(qū)代碼段原子操作,通過線程互斥鎖mutex,也可以使用輕量級(jí)的無(wú)鎖實(shí)現(xiàn)CAS。
C++11的mutex底層實(shí)現(xiàn):
使用strace ./a.out跟蹤代碼,使用C++11提供的mutex,Linux底層使用的也是自己的pthread_mutex互斥鎖。
1.2、線程間的同步通信
- 線程間不通信的話,每個(gè)線程受CPU的調(diào)度,沒有任何執(zhí)行上的順序可言,線程1和線程2是根據(jù)CPU調(diào)度算法來(lái)的,兩個(gè)線程都有可能先運(yùn)行,是不確定的,線程間的運(yùn)行順序是不確定的;
- 所以多線程程序出問題,難以復(fù)現(xiàn),因?yàn)檎l(shuí)也不知道當(dāng)時(shí)線程執(zhí)行的先后順序,我們一般可以得到每個(gè)線程的線程棧信息來(lái)分析是否發(fā)生死鎖的問題之類的。
- 我們要保證線程間的運(yùn)行順序
通信就是:
- 線程1和線程2一起運(yùn)行,線程2要做的事情必須先依賴于線程1完成部分的事情,然后告訴線程2這部分東西做好了,線程2就可以繼續(xù)向下執(zhí)行了。
- 或者是線程1接下來(lái)要做某些操作,這些操作需要線程2把另外一部分事情做完,然后通知一下線程1它做完了,然后線程1才能做這些操作。
生產(chǎn)者,消費(fèi)者線程模型
2、生產(chǎn)者-消費(fèi)者線程模型
注意: C++ STL所有的容器都不是線程安全的,都需要進(jìn)行封裝。
如果直接使用queue,使用queue的push和pop操作時(shí),會(huì)涉及線程安全問題。我們直接在queue的基礎(chǔ)上,直接將其封裝成線程安全的queue。
**注意:**線程函數(shù)代碼和后面的main函數(shù)代碼都是不會(huì)變的;
使用lock_gard,不用直接使用互斥鎖的lock和unlock方法,通過棧上的對(duì)象構(gòu)造和出作用域析構(gòu),來(lái)自動(dòng)調(diào)用互斥鎖的lock和unlock方法;
封裝的queue代碼,為什么每次出錯(cuò)都是不一樣的?
消費(fèi)者消費(fèi)的比較快,queue為空時(shí),就會(huì)出錯(cuò);(需要有線程間的通信機(jī)制)
我們需要:生產(chǎn)者生產(chǎn)一個(gè)物品,通知消費(fèi)者消費(fèi)一個(gè);消費(fèi)完了,消費(fèi)者再通知生產(chǎn)者繼續(xù)生產(chǎn)物品
條件變量: 可以精確做線程間的同步通信;
信號(hào)量也可以做,但是做不到生產(chǎn)一個(gè)消費(fèi)一個(gè),這么精確。
注意: 條件變量cv.wait里面?zhèn)鞯氖莡nique_lock,也只能傳unique_lock,因?yàn)閘ock_gard將拷貝構(gòu)造和賦值重載都delete了(實(shí)參到形參是一個(gè)拷貝構(gòu)造的過程,傳不進(jìn)來(lái))
#include <iostream> #include <thread>//多線程的頭文件 #include <mutex>//互斥鎖的頭文件 #include <condition_variable>//條件變量的頭文件 #include <queue>//C++ STL所有的容器都不是線程安全 using namespace std; std::mutex mtx;//定義互斥鎖,做線程間的互斥操作 std::condition_variable cv;//定義條件變量,做線程間的同步通信操作 //生產(chǎn)者生產(chǎn)一個(gè)物品,通知消費(fèi)者消費(fèi)一個(gè);消費(fèi)完了,消費(fèi)者再通知生產(chǎn)者繼續(xù)生產(chǎn)物品 class Queue { public: void put(int val)//生產(chǎn)物品 { unique_lock<std::mutex> lck(mtx);//unique_ptr while (!que.empty()) { //que不為空,生產(chǎn)者應(yīng)該通知消費(fèi)者去消費(fèi),消費(fèi)者消費(fèi)完了,生產(chǎn)者再繼續(xù)生產(chǎn) //生產(chǎn)者線程進(jìn)入#1等待狀態(tài),并且#2把mtx互斥鎖釋放掉 cv.wait(lck);//傳入一個(gè)互斥鎖,當(dāng)前線程掛起,處于等待狀態(tài),并且釋放當(dāng)前鎖 lck.lock() lck.unlock } que.push(val); /* notify_one:通知喚醒另外的一個(gè)線程的 notify_all:通知喚醒其它所有線程的 通知其它所有的線程,我生產(chǎn)了一個(gè)物品,你們趕緊消費(fèi)吧 其它線程得到該通知,就會(huì)從等待狀態(tài) =》 到阻塞狀態(tài) =》 但是要獲取互斥鎖才能繼續(xù)向下執(zhí)行 */ cv.notify_all(); cout << "生產(chǎn)者 生產(chǎn):" << val << "號(hào)物品" << endl; } int get()//消費(fèi)物品 { lock_guard<std::mutex> guard(mtx);//相當(dāng)于scoped_ptr unique_lock<std::mutex> lck(mtx);//相當(dāng)于unique_ptr 更安全 while (que.empty()) { //消費(fèi)者線程發(fā)現(xiàn)que是空的,通知生產(chǎn)者線程先生產(chǎn)物品 //#1 掛起,進(jìn)入等待狀態(tài) #2 把互斥鎖mutex釋放 cv.wait(lck); }//如果其他線程執(zhí)行notify了,當(dāng)前線程就會(huì)從等待狀態(tài) =》到阻塞狀態(tài) =》但是要獲取互斥鎖才能繼續(xù)向下執(zhí)行 int val = que.front(); que.pop(); cv.notify_all();//通知其它線程我消費(fèi)完了,趕緊生產(chǎn)吧 cout << "消費(fèi)者 消費(fèi):" << val << "號(hào)物品" << endl; return val; } private: queue<int> que; }; //這里模擬生產(chǎn)者生產(chǎn)10個(gè)物品,消費(fèi)者消費(fèi)10個(gè)物品 void producer(Queue* que)//生產(chǎn)者線程 { for (int i = 1; i <= 10; ++i) { que->put(i); std::this_thread::sleep_for(std::chrono::milliseconds(100));//睡眠100毫秒 } } void consumer(Queue* que)//消費(fèi)者線程 { for (int i = 1; i <= 10; ++i) { que->get(); std::this_thread::sleep_for(std::chrono::milliseconds(100));//睡眠100毫秒 } } int main() { Queue que; //兩個(gè)線程共享的隊(duì)列 std::thread t1(producer, &que);//開啟生產(chǎn)者線程 std::thread t2(consumer, &que);//開啟消費(fèi)者線程 //主線程等待兩個(gè)子線程都執(zhí)行完再結(jié)束。 t1.join(); t2.join(); return 0; }
3、lock_gard和unique_lock
lock_gard和unique_lock可以看成unique_ptr和scope_ptr之間的關(guān)系,lock_gard和unique_lock做的事情是一樣的,都是在構(gòu)造函數(shù)中國(guó)自動(dòng)執(zhí)行mutex的lock()函數(shù),在析構(gòu)函數(shù)中自動(dòng)執(zhí)行mutex的unlock()函數(shù)。
lock_gard源碼:
unique_lock源碼:
4、流程分析
- 首先消費(fèi)者線程拿到互斥鎖,生產(chǎn)者線程沒有拿到互斥鎖,生產(chǎn)者線程處于阻塞狀態(tài);
- 消費(fèi)者線程發(fā)現(xiàn)que隊(duì)列是空的,進(jìn)入while循環(huán),進(jìn)入的等待狀態(tài),將互斥鎖釋放(都是由條件變量控制的);
- 此時(shí)生產(chǎn)者線程拿到互斥鎖,不進(jìn)入while循環(huán),生產(chǎn)對(duì)象放入que隊(duì)列中,notify_all通知其他線程,也就是消費(fèi)者線程,由等待狀態(tài)進(jìn)入阻塞狀態(tài), 當(dāng)生產(chǎn)者線程函數(shù)完之后,出了函數(shù)作用域,將mutex互斥鎖釋放了,消費(fèi)者線程拿到鎖了,阻塞狀態(tài)變?yōu)檫\(yùn)行狀態(tài),繼續(xù)向下執(zhí)行;
- 消費(fèi)者線程消費(fèi)完之后,繼續(xù)通知生產(chǎn)者,我消費(fèi)完了。
到此這篇關(guān)于C++詳細(xì)分析線程間的同步通信的文章就介紹到這了,更多相關(guān)C++線程同步通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言創(chuàng)建數(shù)組實(shí)現(xiàn)函數(shù)init,empty,reverse
這篇文章主要介紹了C語(yǔ)言創(chuàng)建數(shù)組實(shí)現(xiàn)函數(shù)init,empty,reverse,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07C++中的局部變量、全局變量、局部靜態(tài)變量、全局靜態(tài)變量的區(qū)別
本文主要介紹了C++中的局部變量、全局變量、局部靜態(tài)變量、全局靜態(tài)變量的區(qū)別。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02C語(yǔ)言超詳細(xì)講解棧與隊(duì)列實(shí)現(xiàn)實(shí)例
棧和隊(duì)列,嚴(yán)格意義上來(lái)說(shuō),也屬于線性表,因?yàn)樗鼈円捕加糜诖鎯?chǔ)邏輯關(guān)系為?"一對(duì)一"?的數(shù)據(jù),但由于它們比較特殊,因此將其單獨(dú)作為一章,做重點(diǎn)講解2022-03-03C++使struct對(duì)象擁有可變大小的數(shù)組(詳解)
下面小編就為大家?guī)?lái)一篇C++使struct對(duì)象擁有可變大小的數(shù)組(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2016-12-12C++基本用法實(shí)踐之移動(dòng)語(yǔ)義詳解
移動(dòng)(move)語(yǔ)義是C++引入了一種新的內(nèi)存優(yōu)化,以避免不必要的拷貝,下面小編就來(lái)和大家簡(jiǎn)單聊聊C++中移動(dòng)語(yǔ)義的相關(guān)使用吧,希望對(duì)大家有所幫助2023-07-07詳解如何實(shí)現(xiàn)C++虛函數(shù)調(diào)用匯編代碼
多態(tài)是C++中最重要的特性之一,對(duì)虛函數(shù)的調(diào)用在C++代碼中是隨處可見的,本篇文章我們?cè)敿?xì)探討一下,感興趣的朋友快來(lái)看看吧2021-11-11