一、互斥量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