??互斥量和死鎖
一、資料共享問題
首先,我們看看多執行緒的執行順序:
void TextThread() {
cout << "我是執行緒:" << this_thread::get_id() << endl;
//執行緒內操作代碼
cout << "執行緒" << this_thread::get_id() << "操作結束" << endl;
}
int main()
{
vector<thread> threadVec;
for (int i = 0; i < 5; ++i) {
threadVec.push_back(thread(TextThread));
}
for (int i = 0; i < 5; ++i) {
threadVec[i].join();
}
return 0;
}

- 把thread物件放入到容器中管理,看起來像個thread物件陣列,對一次創建大量的執行緒并對大量執行緒進行管理有好處;
- 多個執行緒執行順序是亂的,跟作業系統內部對執行緒的運行調度機制有關;
然而上述的執行緒中并沒有涉及到執行緒之間的通信問題,如果涉及多個執行緒操作同一堆資料,會怎么樣呢?(學過作業系統都知道,這就是資料共享問題)
- 如果是執行緒只讀資料,是安全穩定的,不會存在問題;
- 如果是有讀有寫:①不加處理就會執行出錯,如對同一個資料同時讀和寫,比如簡單的一個++i,底層是三四潭訓編代碼,運行程序中(還沒有執行完++i操作)執行緒就被調度了,而其他執行緒又用這個i值,又++i,導致值錯亂!;②最簡單的防止崩潰方法:讀的時候不能寫,寫的時候不能讀,
二、互斥量
2.1 互斥量的基本概念
- c++中互斥量就是個類物件,可以理解為一把鎖,多個執行緒嘗試用lock()成員函式來加鎖,只有一個執行緒能鎖定成功,如果沒有鎖成功,那么流程將卡在lock()這里不斷嘗試去鎖定,
- 互斥量使用要小心,保護資料不要多也不要少,少了達不到效果,多了影響效率,
2.2 互斥量的用法
頭檔案包含#include <mutex>
最基礎用法
①lock()、unlock()
-
步驟:1.lock(); 2.操作共享資料;3.unlock();
-
lock()和unlock()要成對使用,注意這種情況:
//拿取資料的函式 bool outMsgPro(int& command) { myMutex.lock(); if (!msgRecvQueue.empty()) {//非空就進行操作 int command = msgRecvQueue.front(); msgRecvQueue.pop(); //因為進入這里也會return了,一定要unlock(); myMutex.unlock(); return true; } myMutex.unlock(); //其他操作代碼 return false; }
高級一點的寫法
②lock_guard類模板
lock_guard<mutex> myGuard(myMutex)直接取代了myMutex.lock()和myMutex.unlock();- lock_guard建構式執行了mutex::lock();在出作用域,呼叫解構式時,執行mutex::unlock()
- 可以加上
{},約束lock_guard的作用域;
//拿取資料的函式
bool outMsgPro(int& command) {
{
std::lock_guard<std::mutex> myGuard(myMutex);
if (!msgRecvQueue.empty()) {//非空就進行操作
int command = msgRecvQueue.front();
msgRecvQueue.pop();
return true;
}
}
//其他操作代碼
return false;
}
三、死鎖
3.1 死鎖演示
產生死鎖的條件:至少有兩個互斥量,多個執行緒同時需要這兩個互斥量,最終形成倍訓,比如:
- a. 執行緒A執行時,這個執行緒先鎖mutex1,并且鎖成功了,然后去鎖mutex2的時候,出現了背景關系切換,
- b. 執行緒B執行,這個執行緒先鎖mutex2,因為mutex2沒有被鎖,即mutex2可以被鎖成功,然后執行緒B要去鎖mutex1.
- c. 此時,死鎖產生了,A鎖著mutex1,需要鎖mutex2,B鎖著mutex2,需要鎖mutex1,形成倍訓,沒法繼續運行,
3.2 死鎖解決方案
只要保證多個互斥量上鎖的順序一樣就不會造成死鎖!
3.3 std::lock()函式模板
std::lock(mutex1,mutex2……):一次鎖定多個互斥量(一般這種情況很少),用于處理多個互斥量,但是鎖要單獨解開mutex1.unlock(),mutex2.unlock()都要自己寫- 該函式:如果互斥量中一個都沒鎖住,它就等著,等所有互斥量都鎖住,才能繼續執行,如果有一個沒鎖住,就會把已經鎖住的釋放掉(即要么互斥量都鎖住,要么都沒鎖住,自動防止死鎖)
3.4 std::lock_guard和std::adopt_lock引數
-
std::lock(mutex1,mutex2); lock_guard<mutex> myGuard1(mutex1, adopt_lock); lock_guard<mutex> myGuard2(mutex2, adopt_lock);用lock_guard構造mutex1、mutex2鎖的物件,加入adopt_lock后,在呼叫lock_guard的建構式時,不再進行lock(); 但是在出了物件的作用域后,還是會呼叫unlock()釋放鎖! 解決了lock多個鎖后需要自己每個釋放的問題,
-
std::adopt_lock為結構體物件,起一個標記作用,表示這個互斥量已經lock(),不需要再lock()操作,
整個示例代碼:
class A
{
public:
//拿取資料的函式
bool outMsgPro(int& command) {
lock(mutex1, mutex2);
lock_guard<mutex> myGuard1(mutex1, adopt_lock);
lock_guard<mutex> myGuard2(mutex2, adopt_lock);
//②lock_guard<mutex> myGuard1(mutex1); lock_guard<mutex> myGuard2(mutex2);
//①mutex1.lock(); mutex2.lock();
if (!msgRecvQueue.empty()) {//非空就進行操作
command = msgRecvQueue.front();
msgRecvQueue.pop();
//因為進入這里也會return了,一定要unlock();
//mutex1.unlock(); mutex2.unlock();
return true;
}
//mutex1.unlock(); mutex2.unlock();
return false; //return自動釋放鎖
}
//寫入資料函式;
void inMsgPro() {
for (int i = 0; i < 100; ++i) {
cout << "inMsgPro()執行,插入元素" << i << endl;
lock(mutex1, mutex2);
lock_guard<mutex> myGuard1(mutex1, adopt_lock);
lock_guard<mutex> myGuard2(mutex2, adopt_lock);
//②lock_guard<mutex> myGuard1(mutex1); lock_guard<mutex> myGuard2(mutex2);
//①mutex1.lock(); mutex2.lock();
msgRecvQueue.push(i);
//mutex1.unlock(); mutex2.unlock();
}
}
//測驗拿出資料函式
void Test() {
int data;
for (int i = 0; i < 100; ++i) {
if (outMsgPro(data)) cout << data << " ";
else cout << "沒有資料" << " ";
}
}
private:
queue<int> msgRecvQueue;
mutex mutex1;
mutex mutex2;
};
int main() {
A a;
thread myInMsgObj(&A::inMsgPro, &a);//必須要傳入地址,說明是同一個元素
thread myOutMsgObj(&A::Test, &a);
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/472845.html
標籤:C++
