




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第C++多線程之互斥鎖與死鎖目錄1.前言2.互斥鎖2.1互斥鎖的特點2.2互斥鎖的使用2.3std::lock_guard3.死鎖3.1死鎖的含義3.2死鎖的例子3.3死鎖的解決方法
1.前言
比如說我們現在以一個list容器來模仿一個消息隊列,當消息來臨時插入list的尾部,當讀取消息時就把頭部的消息讀出來并且刪除這條消息。在代碼中就以兩個線程分別實現消息寫入和消息讀取的功能,如下:
classmsgList
private:
listintmylist;//用list模仿一個消息隊列
public:
voidWriteList()//向消息隊列中寫入消息(以i作為消息)
for(inti=0;i100000;i++)
cout"Write:"iendl;
mylist.push_back(i);
return;
voidReadList()//從消息隊列中讀取并取出消息
for(inti=0;i100000;i++)
if(!mylist.empty())
cout"Read:"mylist.front()endl;
mylist.pop_front();
else
cout"MessageListisempty!"endl;
intmain()
msgListmlist;
threadpread(msgList::ReadList,mlist);//讀線程
threadpwrite(msgList::WriteList,mlist);//寫線程
//等待線程結束
pread.join();
pwrite.join();
return0;
}
這段程序在運行過程中,大部分時間是正常的,但是也會出現如下不穩(wěn)定的情況:
為什么會出現這種情況呢?
這是因為消息隊列對于讀線程和寫線程來說是共享的,這時就會出現兩種特殊的情況:讀線程的讀取操作還沒有結束,線程上下文就切換到了寫線程中;或者寫線程的寫入操作還沒有結束,線程上下文切換就到了讀線程中,這兩種情況都反映了讀寫沖突,從而出現了以上錯誤。
要想解決這個問題,最顯然最直接的方法就是將讀寫操作分離開來,讀的時候不允許寫,寫的時候不允許讀,這樣,才能實現線程安全的讀和寫。說形象一點,就是在進行讀操作時,就對共享資源進行加鎖,禁止其他線程訪問,其他線程要訪問就得等到讀線程解鎖才行,就像上廁所一樣,一次只能上一個人,其他人必須得等他上完了再上。這樣,就有了互斥鎖的概念。
2.互斥鎖
在多任務操作系統(tǒng)中,同時運行的多個任務可能都需要使用同一種資源。比如說,同一個文件,可能一個線程會對其進行寫操作,而另一個線程需要對這個文件進行讀操作,可想而知,如果寫線程還沒有寫結束,而此時讀線程開始了,或者讀線程還沒有讀結束而寫線程開始了,那么最終的結果顯然會是混亂的。為了保護共享資源,在線程里也有這么一把鎖——互斥鎖(mutex),互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態(tài),即上鎖(lock)和解鎖(unlock)。
2.1互斥鎖的特點
1.原子性:把一個互斥量鎖定為一個原子操作,這意味著如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以成功鎖定這個互斥量;
2.唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量;
3.非繁忙等待:如果一個線程已經鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不占用任何cpu資源),直到第一個線程解除對這個互斥量的鎖定為止,第二個線程則被喚醒并繼續(xù)執(zhí)行,同時鎖定這個互斥量。
2.2互斥鎖的使用
根據前面我們可以知道,互斥鎖主要就是用來保護共享資源的,在C++11中,互斥鎖封裝在mutex類中,通過調用類成員函數lock()和unlock()來實現加鎖和解鎖。值得注意的是,加鎖和解鎖,必須成對使用,這也是比較好理解的。除此之外,互斥量的使用時機,就以開篇程序為例,我們要保護的共享資源當然就是消息隊列l(wèi)ist了,那么互斥鎖應該加在哪里呢?
可能想的比較簡單一點:就直接把鎖加在函數最前面不就好了么?如下所示:
classmsgList
private:
listintmylist;//用list模仿一個消息隊列
mutexmtx;//創(chuàng)建互斥鎖對象
public:
voidWriteList()//向消息隊列中寫入消息(以i作為消息)
mtx.lock();
for(inti=0;i100000;i++)
cout"Write:"iendl;
mylist.push_back(i);
mtx.unlock();
return;
//.......
};
不過如果這樣加鎖的話,要等寫線程完全執(zhí)行結束才能開始讀線程,讀寫線程變成了串行執(zhí)行,這就違背了線程并發(fā)性的特點了。正確的加鎖方式應當是在執(zhí)行寫操作的具體部分加鎖,如下所示:
classmsgList
private:
listintmylist;//用list模仿一個消息隊列
mutexmtx;//創(chuàng)建互斥鎖對象
public:
voidWriteList()//向消息隊列中寫入消息(以i作為消息)
for(inti=0;i100000;i++)
mtx.lock();
cout"Write:"iendl;
mylist.push_back(i);
mtx.unlock();
return;
//.......
};
這樣,才能真正的實現讀寫互不干擾。
下面再舉一個更為直觀的例子,創(chuàng)建兩個線程同時對list進行寫操作:
classmsgList
private:
listintmylist;
mutexm;
inti=0;
public:
voidWriteList()
while(i1000)
mylist.push_back(i++);
return;
voidshowList()
for(autop=mylist.begin();p!=mylist.end();p++)
cout(*p)"";
coutendl;
cout"sizeoflist:"mylist.size()endl;
return;
intmain()
msgListmlist;
threadpwrite0(msgList::WriteList,mlist);
threadpwrite1(msgList::WriteList,mlist);
pwrite0.join();
pwrite1.join();
cout"threadsend!"endl;
mlist.showList();//子線程結束后主線程打印list
return0;
}
這里用兩個線程來寫list,并且最終在主線程中調用了showList()來輸出list的size和所有元素,我們先來看下輸出情況:
根據結果可以看到,這里有很多問題:實際輸出的元素個數和size不符,輸出的元素也并不是連續(xù)的,這都是多個線程同時更新list所造成的情況。這種情況下,運行結果是無法預料的,每次都可能不一樣。這就是線程不安全所引發(fā)的問題,我們加上鎖再來看看:
classmsgList
private:
listintmylist;
mutexm;
inti=0;
public:
voidWriteList()
while(i1000)
m.lock();//加鎖
mylist.push_back(i++);
m.unlock();//解鎖
return;
//......
};
這樣加鎖就正確了嗎?我們再多運行幾次看看:
數字都是連續(xù)的,但是個數卻多了一個(出現的幾率還是比較?。?,這又是什么原因造成的呢?還是兩個線程的問題,假設要插入1000個數,循環(huán)條件就是while(i1000),當i=999的時候兩個寫線程都可以進入while循環(huán),此時如果pwrite0線程拿到了lock(),那么pwrite1線程就只能一直等待,pwrite0線程繼續(xù)往下執(zhí)行,使得i變成了1000,此時,對于pwrite0線程來說,它就必須退出循環(huán)了。而此時的pwrite1在哪里呢?還等在lock()的地方,pwrite0線程unlock()后,pwrite1成功lock(),此時i=1000,但是pwrite1卻還沒有執(zhí)行完此次循環(huán),因此向list中插入1000,此時退出的i的值為1001,這也就造成了實際輸出為1001個數的情況。
為了避免這個問題,一個簡單的辦法就是在lock()之后再加上一個判斷,判斷i是否依舊滿足while的條件,如下:
voidWriteList()
while(i10000)
m.lock();
if(i=10000)
m.unlock();//退出之前必須先解鎖
break;
mylist.push_back(i++);
m.unlock();
return;
}
為什么這里要在break前面加一個unlock()呢?原因就在于:如果break前面沒有unlock(),一旦i符合了if的條件,就直接break了,此時就沒法unlock(),程序就會報錯:
可以發(fā)現,這種錯誤是比較難發(fā)現的,特別是像這樣程序中出現了分支的情況,很容易就使得程序實際運行時lock()了卻沒有unclock()。為了解決這一問題,就有了std::lock_guard。
2.3std::lock_guard
簡單來理解的話,lock_guard就是一個類,它會在其構造函數中加鎖,而在析構函數中解鎖,也就是說,只要創(chuàng)建一個lock_guard的對象,就相當于lock()了,而該對象析構時,就自動調用unlock()了。
就以上述程序為例,直接改寫為:
voidWriteList()
while(i10000)
lock_guardmutexguard(m);//創(chuàng)建lock_guard的類對象guard,用互斥量m來構造
//m.lock();
if(i=10000)
//m.unlock();//由于有了guard,這里就無需unlock()了
break;
mylist.push_back(i++);
//m.unlock();
return;
}
這里主要有兩個需要注意的地方:第一、原先的lock()和unlock()都不用了;第二、if中的break前面也不用再調用unlock()了。這都是因為對象guard在lock_guard一句處構造出來,同時就調用了lock(),當退出while時,guard析構,析構時就調用了unlock()。(局部對象的生命周期就是創(chuàng)建該對象時離其最近的大括號的范圍{})
3.死鎖
3.1死鎖的含義
死鎖是什么意思呢?舉個例子,我和你手里都拽著對方家門的鑰匙,我說:“你不把我的鎖還來,我就不把你的鎖給你!”,你一聽不樂意了,也說:“你不把我的鎖還來,我也不把你的鎖給你!”就這樣,我們兩個人互相拿著對方的鎖又等著對方先把鎖拿來,然后就只能一直等著等著等著......最終誰也拿不到自己的鎖,這就是死鎖。
顯然,死鎖是發(fā)生在至少兩個鎖之間的,也就是指由于兩個或者多個線程互相持有對方所需要的資源,導致這些線程處于等待狀態(tài),無法前往執(zhí)行,當線程互相持有對方所需要的資源時,會互相等待對方釋放資源,如果線程都不主動釋放所占有的資源,將產生死鎖。
3.2死鎖的例子
mutexm0,m1;
inti=0;
voidfun0()
while(i100)
lock_guardmutexg0(m0);//線程0加鎖0
lock_guardmutexg1(m1);//線程0加鎖1
cout"thread0running..."endl;
return;
voidfun1()
while(i100)
lock_guardmutexg1(m1);//線程1加鎖1
lock_guardmutexg0(m0);//線程1加鎖0
cout"thread1running..."iendl;
return;
intmain()
threadp0(fun0);
threadp1(fun1);
p0.join();
p1.join();
return0;
}
我們來看下運行結果:
這就出現了死鎖。產生的原因就是因為在線程0中,先加鎖0,再加鎖1;在線程1中,先加鎖1,再加鎖0;如果兩個線程之一能夠完整執(zhí)行的話,那自然是沒有問題的,但是如果某個時刻,線程0中剛加鎖0,就上下文切換到線程1,此時線程1就加鎖1,然后此時兩個線程都想向下執(zhí)行的話,線程1就必須等待線程0解鎖0,線程0就必須等待線程1解鎖1,就這樣兩個線程都一直阻塞著,形成了死鎖。
3.3死鎖的解決方法
①按順序加鎖
以上述例程來說,就是線程0和線程1的加鎖順序保持一致,如下所示:
mutexm0,m1;
inti=0;
voidfun0()
while(i100)
lock_guardmutexg0(m0);//線程0加鎖0
lock_guardmutexg1(m1);//線程0加鎖1
cout"thread0running..."endl;
return;
voidfun1()
while(i100)
lock_guardmutexg0(m0);//線程1加鎖0
lock_guardmutexg1(m1);//線程1加鎖1
cout"thread1running..."iendl;
return;
intmain()
threadp0(fun0);
threadp1(fun1);
p0.join();
p1.join();
return0;
}
在這種情況下,兩個線
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 企業(yè)內部金融交易的區(qū)塊鏈解決方案案例
- 醫(yī)療領域智能診斷系統(tǒng)的監(jiān)管框架及實施
- 乳膠合同范例
- 買房屋正規(guī)合同范例
- 中考動員學生發(fā)言稿模版
- 紅色扁平風食品安全模板
- 網站編輯個人工作總結模版
- 醫(yī)療大數據庫建設與疾病預防控制策略研究
- 公司之間購銷合同范例
- 區(qū)塊鏈在教育公平中的角色與挑戰(zhàn)
- 培訓近三年個人工作總結
- 小學語文二年級上冊《去外婆家》教學設計二
- 2024年《金融市場基礎知識》沖刺復習講義
- 2024多級AO工藝污水處理技術規(guī)程
- 電影鑒賞評論智慧樹知到期末考試答案章節(jié)答案2024年山東藝術學院
- 2023-2024學年廣東省惠州市惠東縣七年級(下)期末數學試卷(含答案)
- 2024年四川省綿陽市涪城區(qū)綿陽外國語實驗學校小升初數學試卷(一)
- JGJ144-2019外墻外保溫工程技術標準
- 人教精通六年級下冊英語單詞默寫表
- JB-T 8236-2023 滾動軸承 雙列和四列圓錐滾子軸承游隙及調整方法
- MOOC 移動通信-河海大學 中國大學慕課答案
評論
0/150
提交評論