互斥量:
多個線程同時訪問共享數據時可能會衝突,這跟信號的可重性是同樣的問題。如 果兩個線程都要把某個全局變量增加1,這個操作在某平臺需要三條指令完成:
1. 從內存讀變量值到寄存器
2. 寄存器的值加1
3. 將寄存器的值寫回內存
先舉個例子:創建兩個線程,各把counter增加5000次,正常情況下最後counter應該等於10000。
代碼實現如下:
結果:
可以看到,每次運行程序的結果都不一樣。說明在調用過程中發生了互斥現象。
解決辦法:加互斥鎖
實現多線程同步可以引互斥鎖(Mutex,Mutual Exclusive Lock),獲得鎖的線程可以完成“讀-修改-寫”的操作,然後釋放鎖給其它線程,沒有獲得鎖的線程只能等待不能訪問共享數據,這樣“讀-修改-寫”三步操作組成個原操作,要麼都執,要麼都不執,不會執到中間被打斷,也不會在其它處理器上並做這個操作。 Mutexpthread_mutex_t類型的變量表,可以這樣初始化和銷燬。
相關函數如下:
pthread_mutex_init函 數初始化的Mutex可以pthread_mutex_destroy銷燬。 如果Mutex變量是靜態分配的(全局變量 或static變量),也可以宏定義PTHREAD_MUTEX_INITIALIZER來初始化,相當於 pthread_mutex_init初始化並且attr參數爲NULL。 Mutex的加鎖和解鎖操作可以下列函數
一個線程可以調pthread_mutex_lock獲得Mutex,如果這時另一個線程已經調pthread_mutex_lock獲得了該Mutex,則當前線程需要掛起等待,直到另一個線程調pthread_mutex_unlock釋放Mutex,當前線程被喚醒,才能獲得該Mutex並繼續執。如果這個線程既想獲得鎖,又不想掛起等待,可以調pthread_mutex_trylock,如果Mutex已經被 另一個線程獲得,這個函數會失敗返回EBUSY,不會使線程掛起等待。
現在給上一個例子加上互斥鎖,代碼如下:
運行結果如下:
可以看到,加鎖後實現了線程同步。
死鎖原理:
根據操作系統中的定義:死鎖是指在一組進程中的各個進程均佔有不會釋放的資源,但因互相申請被其他進程所佔用不會釋放的資源而處於的一種永久等待狀態。
死鎖的四個必要條件:
1、互斥條件(Mutual exclusion):資源不能被共享,只能由一個進程使用。
2、請求與保持條件(Hold and wait):已經得到資源的進程可以再次申請新的資源。
3、非剝奪條件(No pre-emption):已經分配的資源不能從相應的進程中被強制地剝奪。
4、循環等待條件(Circular wait):系統中若干進程組成環路,該環路中每個進程都在等待相鄰進程正佔用的資源
解決死鎖的基本方法:
1、預防死鎖:
資源一次性分配:(破壞請求和保持條件)
可剝奪資源:即當某進程新的資源未滿足時,釋放已佔有的資源(破壞不可剝奪條件)。
資源有序分配法:系統給每類資源賦予一個編號,每一個進程按編號遞增的順序請求資源,釋放則相反(破壞環路等待條件)。
2、避免死鎖:
預防死鎖的幾種策略,會嚴重地損害系統性能。因此在避免死鎖時,要施加較弱的限制,從而獲得較滿意的系統性能。由於在避免死鎖的策略中,允許進程動態地申請資源。因而,系統在進行資源分配之前預先計算資源分配的安全性。若此次分配不會導致系統進入不安全狀態,則將資源分配給進程;否則,進程等待。其中最具有代表性的避免死鎖算法是銀行家算法。
3、檢測死鎖
首先爲每個進程和每個資源指定一個唯一的號碼;
然後建立資源分配表和進程等待表。
4、解除死鎖:
當發現有進程死鎖後,便應立即把它從死鎖狀態中解脫出來,常採用的方法有:
剝奪資源:從其它進程剝奪足夠數量的資源給死鎖進程,以解除死鎖狀態;
撤消進程:可以直接撤消死鎖進程或撤消代價最小的進程,直至有足夠的資源可用,死鎖狀態.消除爲止;所謂代價是指優先級、運行代價、進程的重要性和價值等。