今天是七夕佳節,我又來啦,最近到處跑累死人。
無論任何程式語言,說到多執行緒,我們肯定要非常注意臨界資源的訪問問題。
(個人認為多執行緒互斥的內容是比較多,而且比較複雜的,這次的坑的篇幅可能比較長,盡可能用最通俗的語言來表達)
什麼是臨界資源?
當多個執行緒需要某個資源,而這個資源只能在同一時刻被乙個執行緒所持有。
我們來模擬乙個情景,假如有乙個臨界資源,是個筆記本,有兩位小伙汁,乙個喜歡往筆記本裡寫字,另乙個則喜歡把筆記本上寫的字擦掉。(其實就是生產者和消費者啦)
上**
#include
#include
#include
#include
static qstring g_notebook;
class
threadproducers
:public qthread
void
run()}
};class
threadconsumers
:public qthread
void
run()}
}};int
main
(int argc,
char
*ar**)
大家覺得這樣寫對嗎?生產者拿到筆記本寫字,消費者拿到筆記本擦除寫的字,貌似沒啥毛病。
可是很可惜,這樣子的**其實是會讓你的程式崩潰的哦(長時間執行時)。因為根本沒有做臨界資源的保護嘛。我們能保證消費者拿到資源後別人不能拿到這個資源嗎?如果不能的話豈不是會競爭資源打架駕?
既然臨界資源那麼重要,要怎麼去保護呢?
同樣的,有乙個具體情景,地鐵中的廁所當有人進入廁所並且鎖上了廁所的門之後,門把手處會有乙個提示廁所正在使用的標識,當其他人看到這個標識時就知道裡面有人了,大家就會在外等候,而不是破門而入趕人走。。。。我們也可以像上面的情景一樣用某種標識來保護我們的臨界資源。
工程中的解決方法一般都是使用原子操作來進行臨界資源的保護,給資源加鎖。
用 qmutex 來保護臨界資源
看看我們修改後的**
#include
#include
#include
#include
#include
static qstring g_notebook;
qmutex mtx_0;
class
threadproducers
:public qthread
void
run()}
};class
threadconsumers
:public qthread
void
run(
) mtx_0.
unlock()
;}}}
;int
main
(int argc,
char
*ar**)
我們在取臨界資源前先給資源上鎖,這樣別人就不能隨便修改你持有的資源了,現在就算你執行幾年都不會打架了。
用 qsemaphore 來保護臨界資源
上**
#include
#include
#include
#include
#include
static qstring g_notebook;
qsemaphore sem_0(1
);class
threadproducers
:public qthread
void
run()}
};class
threadconsumers
:public qthread
void
run(
) sem_0.
release()
;}}}
;int
main
(int argc,
char
*ar**)
執行結果和用 qmutex 的其實是一樣的,這裡就不貼出執行結果了(懶)。
那用 qmutex 和用 qsemaphore 有啥區別啊?從剛才的**上看好像感覺不出來,其實 qsemaphore 是乙個特殊的執行緒鎖罷了。qsemaphore 可以保護一定數量的同種資源,qsemaphore 物件中維護了乙個整型值,acquire() 使該值減1,release() 使該值加1,如果該值為 0 時,acquire() 會將執行緒阻塞直到不為 0。
現在我們來模擬乙個情景,乙個生產者和乙個消費者往乙個盒子中放產品和取產品,這個盒子的空間是有限的,能放 10 個產品。
我們用 qsemaphore 來保護這 10 個放產品的空間。
#include
#include
#include
#include
#define size_box 10
int g_box[size_box]=;
qsemaphore sem_free
(size_box)
;qsemaphore sem_use(0
);class
threadproducers
:public qthread
void
run()}
//end of for
}//end of for(;;)}}
;class
threadconsumers
:public qthread
void
run()}
//end of for
}//end of for(;;)}}
;int
main
(int argc,
char
*ar**)
這樣做有啥好處啊?想想。
如果這裡用 qmutex 進行保護,當生產者獲得鎖後,其實生產者自己不知道是不是還有位置放產品的,對倉庫進行輪詢,結果發現沒位置可放,此時消費者又沒辦法從中取產品,浪費了不少時間。消費者同理。
通過維繫兩個訊號量我們可以很好的解決上面的問題,生產者只需要關心標識剩餘空間的訊號量就行了,如果不被阻塞,說明就是有位置給你放東西的,這樣就不會做無意義的輪詢了。消費者同理。
使用訊號量可以有效地提高程式的併發性,使程式更高效。
一般來說,每個臨界資源都應該給其配乙個專門的鎖。
每當說到多個臨界資源的時候,我們就應該要十分小心死鎖。
死鎖是啥玩意啊?當執行緒間相互等待臨界資源而造成彼此無法繼續執行的情況就是死鎖啦。
看看**
#include
#include
#include
#include
int g_resources0 =2;
int g_resources1 =0;
int g_resources2 =1;
int g_resources3 =9;
int g_resources4 =0;
int g_resources5 =8;
int g_resources6 =0;
int g_resources7 =7;
qmutex mtx_0;
qmutex mtx_1;
qmutex mtx_2;
qmutex mtx_3;
qmutex mtx_4;
qmutex mtx_5;
qmutex mtx_6;
qmutex mtx_7;
class
threada
:public qthread
void
run()}
};class
threadb
:public qthread
void
run()}
};intmain
(int argc,
char
*ar**)
上面的**很貼心的給每個臨界資源都配了一把鎖,每次使用臨界資源前都先去獲取該資源的鎖,並且用完了都會把鎖還回去。這麼貼心的**,讓我們來看看執行結果吧
程式只輸出了一句,很明顯不是我們想要的結果。
導致這種情況出現的原因是什麼呢,其實很簡單,死鎖。
執行緒 a 在獲取鎖 3 後還需要獲取鎖 1 才能繼續執行,而執行緒 b 在獲取鎖 1 後需要鎖 3 才能繼續執行。如果 a 和 b 各獲得了一把鎖,兩個人都期望獲得對方持有的鎖,這就出現了死鎖。
發生死鎖的條件
當然滿足什麼的條件並不是一定會出現死鎖的。
既然死鎖很坑爹,那要怎麼去避免呢?
有的人會用乙個鎖來保護很多個臨界資源,比如剛才的**吧,如果所有的臨界資源都用同乙個執行緒鎖來保護,那肯定就不會出現死鎖了。但是這樣會導致效率低下,尤其是像是服務端的程式,很因為獲取不到需要的資源導致客戶端的請求處理超時,多可怕啊。
工程開發中通常是這樣避免死鎖的
所以我們只需要將剛才的**。。。。。(嘿嘿嘿)
Qt多執行緒互斥
目錄 一 多執行緒與臨界資源的依賴 現象分析 二 互斥和解決方法 三 qmutex的主要成員函式和使用 四 示例 五 小結 除了上一節所說的,多執行緒在 執行的時序上會有依賴,那麼其他地方是否還有所依賴呢?答案是有的,也就是與臨界資源的問題,所謂臨界資源是指每次只允許乙個執行緒進行訪問 讀或寫 的資...
Qt 多執行緒同步之互斥鎖
qmutex需要配對使用lock 和unlock 來實現 段的保護 qmutexlocker是另外乙個簡化了互斥量處理的類。qmutexlocker建構函式接受乙個互斥量作為引數並將其鎖定,qmutexlocker的析構函式則將此互斥量解鎖,所以在qmutexlocker例項變數的生存期內的 段得到...
QT多執行緒中的互斥與同步
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!參考自 精通qt4程式設計 qt為實現執行緒的同步與互斥,提供了幾個類。下面主要介紹三個類 一 qmutex和qmutexlocker cpp view plain copy print?class key int createkey int va...