天天看點

五、互斥量概念、用法、死鎖

一、互斥量mutex

保護共享資料,操作時,用代碼把共享資料鎖住,操作資料,解鎖。其他線程要操作共享資料的線程必須等待解鎖,鎖住,操作,解鎖。

互斥量就是類對象,一個鎖,多個線程用lock()成員函數加鎖這個鎖頭,隻有一個線程能鎖成功,成功的标志是lock函數傳回,如果沒有鎖成功,那麼流程就卡在lock()這裡,不斷的嘗試去鎖這個鎖頭。

互斥量使用要小心,保護資料多了,影響效率,保護少了,沒達到保護效果。

二、互斥量用法

1、lock()、unlock()

include <mutex>

先lock,操作共享資料,再unlock。

使用規則:成對使用,有lock必須有unlock;

1 #include <iostream>
 2 #include <thread> //線程
 3 #include <vector>
 4 #include <list>
 5 #include <mutex>
 6 /*
 7 網絡遊戲伺服器
 8 建立兩個線程,一個線程收集玩家指令(用一個數字代表玩家指令),并把指令資料寫到一個隊列中;
 9 另一個線程從隊列中取出玩家發送來的指令,解析,然後執行玩家需要的動作。
10 list:頻繁順序插入和删除資料時效率高;
11 vector:頻繁随機插入和删除資料時效率高;
12 準備使用成員函數來作為線程函數
13 */
14 using namespace std;
15 class A{
16 public:
17     //把收到的消息(玩家指令)讓入到一個隊列中的線程函數
18     void InMsgQue(){
19         for(int i=0;i<100;i++){
20             cout<<"InMsgQue執行,插入一個元素"<<i<<endl;
21             my_mutex.lock();
22             MyQue.push_back(i);//假設i就是指令
23             my_mutex.unlock();
24         }
25 
26     }
27     bool outMsgLULProc(int &command){//這樣的目的是為了友善後序對command的操作
28         my_mutex.lock();
29         if(!MyQue.empty()){
30             int command = MyQue.front();
31             MyQue.pop_front();
32             my_mutex.unlock();//這個unlock特别容易被忘記寫
33             return true;
34         }
35         my_mutex.unlock();
36         return false;
37     }
38     //把資料從消息隊列中取出來的線程函數
39     void OutMsgQue(){
40         int command=0;
41         for(int i=0;i<100;i++){
42             bool result = outMsgLULProc(command);
43             if(result){
44                 //消息不空
45                 cout<<"OutMsgQue正在執行,取出一個元素"<<command<<endl;
46                 //接下來就考慮處理這個取出來的資料command.........
47             }
48             else{
49                 cout << "OutMsgQue執行,但是消息隊列為空"<<i << endl;
50             }
51 
52         }
53         cout<<"end"<<endl;
54     }
55 
56 private:
57     list<int> MyQue;
58     std::mutex my_mutex;//建立一個互斥量
59 };
60 
61 int main(){
62     A myobj;
63     std::thread myout(&A::OutMsgQue,&myobj);
64     std::thread myin(&A::InMsgQue,&myobj);//必須市引用,才能保證線程用的是同一個對象
65     myout.join();
66     myin.join();
67     cout<<"main end"<<endl;
68     return 0;
69 }      

2、std::lock_guard類模闆

為了防止忘記unlock,引入lock_guard,類似于智能指針,他會幫你unlock。

1 bool outMsgLULProc(int &command){
2        std::lock_guard<std::mutex> guardd(my_mutex);
3         if(!MyQue.empty()){
4             int command = MyQue.front();
5             MyQue.pop_front();
6             return true;
7         }
8         return false;
9     }      

lock_guard構造函數執行了lock;析構函數執行了unlock。

隻有return的時候才unlock,沒有分開使用lock和unlock靈活。

可以用大括号提前結束guard:

1     void InMsgQue(){
 2         for(int i=0;i<100;i++){
 3             cout<<"InMsgQue執行,插入一個元素"<<i<<endl;
 4             {
 5                 std::lock_guard<std::mutex> sbguard(my_mutex);
 6                 MyQue.push_back(i);//假設i就是指令
 7             }//大括号結束,lock_guard析構函數執行
 8             //其他處理代碼、、、、、、、
 9         }
10 
11     }      

三、死鎖

1、示範

舉例:張三在北京等李四,不挪窩;李四在深圳等張三,不挪窩。

c++中:

死鎖問題是有至少兩個鎖頭也就是兩個互斥量才會産生。

有兩個鎖:金鎖,銀鎖

線程:A 和 B

線程A先鎖金鎖,金鎖鎖成功,然後去lock銀鎖。。。

線程B先鎖銀鎖,銀鎖還沒被鎖的話,鎖銀鎖成功了,然後去lock金鎖。。。但是金鎖被A鎖了。。。

A拿不到銀鎖,金鎖解不開,B去拿金鎖,但是金鎖被A鎖了,是以銀鎖解不開。

這時就發生死鎖了。AB兩個線程就幹等着直到永遠~~

2、解決方案

隻要保證兩個互斥量的lock順序一緻,就不會發生死鎖。

3、std::lock()函數模闆

用來處理多個互斥量。

可以一次鎖住兩個或多個互斥量。

它不存在由于多個線程中因為鎖的順序問題導緻的死鎖。

如果一個鎖沒鎖住,他就會馬上解鎖之前所有鎖上的互斥量,然後在那兒等着,等所有的互斥量都能夠鎖住,它才能往下走。

要麼都鎖住,要麼都沒有鎖住。

1     void InMsgQue(){
 2         for(int i=0;i<100;i++){
 3             cout<<"InMsgQue執行,插入一個元素"<<i<<endl;
 4             std::lock(my_mutex1,my_mutex2);//相當于兩個互斥量都調用了lock
 5             MyQue.push_back(i);//假設i就是指令
 6             my_mutex1.unlock();
 7             my_mutex2.unlock();
 8             //其他處理代碼、、、、、、、
 9         }
10 
11     }      

容易忘記unlock

4、std::lock_guard的std::adopt_lock參數

使用lock模闆,還是容易忘記unlock,那麼可是使用lock_guard的adopt_lock參數:

1     void InMsgQue(){
2         for(int i=0;i<100;i++){
3             cout<<"InMsgQue執行,插入一個元素"<<i<<endl;
         std::lock(my_mutex1,my_mutex2);
4             std::lock_guard<std::mutex> sbguard(my_mutex1,std::adopt_lock);
5             std::lock_guard<std::mutex> sbguard2(my_mutex2,std::adopt_lock);
6             MyQue.push_back(i);//假設i就是指令
7             //其他處理代碼、、、、、、、
8         }
9     }      

這個參數就是起一個标記作用,就是如果互斥量已經被lock了,那就不會再調用lock_guard的構造函數了,就隻剩析構函數了

轉載于:https://www.cnblogs.com/pacino12134/p/11232607.html