通常在涉及到多線程和多進程操作共享數據時候,不可避免的會涉及到公共數據的互斥訪問,可能會用到互斥鎖,事件,信號量等,然而在使用這些鎖機制的同時肯定會降低系統的並發性,同時如果使用不當可能還會造成一些死鎖等很讓人反感的問題。
其實大部分時候可以通過一些細節的設計來避免鎖機制,共享數據的互斥訪問總的來說可以分為以下兩種:
1、同時修改
先來個例子說明下同時修改數據造成數據不一致的問題:
假設有A,B兩個進程對共享內存中的變量m = 10進行自加運算,當A進程將變量m = 10從內存讀入cpu的寄存器后,這時B進程也將m = 10讀入寄存器,
那么A進程加運算得到11,B進程也讀入10得到11,最終A和B進程將11從寄存器寫回內存,那么m=11,本來做了兩次自增,卻只加了1,肯定不是我們要的結果。
通常直接的解決辦法是使用互斥鎖,自然可以避免這種情況。
如果要避免使用鎖機制, 可以考慮這么做:
在設計的時候就需要對系統各個模塊的功能做比較明確的划分,每一個模塊只負責修改自己的數據,通過發送消息通知其他模塊修改數據。
比如從業務邏輯上A進程只負責共享數據段1的修改,B進程只負責共享數據段2的修改,那么當B進程需要修改數據段1的數據時候,發送消息給A進程,讓A進程去修改,同樣
A進程對數據段2的修改也通過B去修改。
總之就是要從業務邏輯上將各個進程的功能分開,那每個進程修改自己的共享段數據,那么就不需要擔心同時修改造成的數據不一致問題了。在這里設為共享是為了方便訪問對方的數據,下面會講寫入和讀取時候怎么保證。
2、一邊寫入一邊讀取
如果操作不當,這個情況下,通常會出現讀取的那一方讀取的數據不完整。
我們知道CPU每次從內存讀取或者寫入的數據跟CPU的引腳數有關,如果是32位的計算機,也就是CPU引腳有32個,一次能讀取或者寫入32位數據,那么下面的程序是不會存在數據不完整的問題:
公共數據:
int m = 0;
進程A:
while(true)
{
m++;
}
進程B:
if(m%10 == 0)
{
...
}
因為,一個int類型的數據占4字節,也就是32位,那么在32引腳的cpu上,從寄存器通過總線設備寫回內存時候肯定是一次寫回來,也就是B進程訪問變量m時候,m不會出現不完整的情況。
這個寫入的過程有點類似原子操作(當然,A進程從讀取m到寫回內存,肯定不是原子操作),只是在這個過程中,數據是處於一個完整狀態。
再說說一個數據不完整,會被撕裂的例子,在32位的計算機上:
公共數據:
INT64 m = 0x00000000bbbbbbbb;
進程A:
while(true)
{
m = m & 0xaaaaaaaaffffffff;
}
進程B:
if(m%10 == 0)
{
...
}
與上個程序唯一不同的是m的類型64位的,也就是說在32位引腳cpu上,A進程將改變m的值后,從寄存器寫回內存,至少需要寫入兩次,假設第一次寫入高32位,oxaaaaaaaa,第二次寫入減1后的低32位,0xbbbbbbbc,那么如果A進程剛寫完高位數據回內存,時間片切換到B進程,B在讀取的時候可能就會讀取到一半的數據的情況,可能還是之前的值0xaaaaaaaabbbbbbbb,這樣數據就會被撕裂,訪問的數據不完整。
如果是在64位的計算機上(當然操作系統也要64位啊,不然32位的系統可能還是分兩次寫入),就不會存在這種情況。
那么如何避免這種情況呢:
使用dirty機制:具體的做法就是使用一個變量來判斷我們需要訪問的數據的狀態,還是上面的例子,代碼如下:
公共數據:
class Data
{
public:
int a;
int b;
};
Data data;
int dirty = 0;
進程A:
while(true)
{
if(dirty == 0)
{
data.a = 100;
data.b = 200;
dirty = 1;
}
}
進程B:
/* 檢測數據是否被改變過 */
while(true)
{
if(dirty == 1)
{
int a = data.a;
int b = data.b;
dirty = 0;
....
}
}
上面已經說過,對dirty的訪問,不存在不完整的情況,因此大部分時候可以通過對dirty機制,來解決數據一邊讀一邊寫帶來的不完整性。
當然上面這個程序在效率方面和使用鎖機制基本差不多,因為A進程和B進程都需要依賴對方去做操作,那么當條件不滿足的時候,在自己的時間片內都不會改或者讀數據。
其實,如果A進程不頻繁修改數據,那么B進程通過dirty機制判斷,效率肯定是比使用鎖機制高的。
因此,在多線程或者多進程的程序中,設計的時候如果能夠避免鎖機制,可以盡量避免使用鎖機制,可以降低系統的復雜性,避免鎖機制帶來的死鎖等問題,而且大部分時候還可以提高系統的並發性。
第一次寫blog,希望以后能把自己做開發過程中的一些東西多總結,如果有什么不對的地方,歡迎指出。