線程同步是多線程程序設(shè)計的核心內(nèi)容,它的目的是正確處理多線程并發(fā)時的各種問題,例如線程的等待、多個線程訪問同一數(shù)據(jù)時的互斥,防死鎖等。Win32提供多種內(nèi)核對象和手段用于線程同步,如互斥量、信號量、事件、臨界區(qū)等。所不同的是,互斥量、信號量、事件都是Windows的內(nèi)核對象,當程序?qū)@些對象進行控制時會自動轉(zhuǎn)換到核心態(tài),而臨界區(qū)本身不是內(nèi)核對象,它是工作在用戶態(tài)的。我們知道從用戶態(tài)轉(zhuǎn)換到核心態(tài)是需要以時間為代價的,所以如果能在用戶態(tài)就簡單解決的問題,就可以不必勞煩核心態(tài)了。
這里我要說的是兩種用于C++的多線程同步類,通過對這兩種類的使用就可以方便的實現(xiàn)對變量或代碼段的加鎖控制,從而防止多線程對變量不正確的操作。
所謂加鎖,就是說當我們要訪問某關(guān)鍵變量之前,都需要首先獲得允許才能繼續(xù),如果未獲得允許則只有等待。一個關(guān)鍵變量擁有一把鎖,一個線程必須先得到這把鎖(其實稱為鑰匙可能更形象)才可以訪問這個變量,而當某個變量持有這把鎖的時候,其他線程就不能重復(fù)的得到它,只有等持有鎖的線程把鎖歸還以后其他線程才有可能得到它。之所以這樣做,就是為了防止一個線程讀取某對象途中另一線程對它進行了修改,或兩線程同時對一變量進行修改,例如:
// 全局:
struct MyStruct ... { int a, b; } ;
MyStruct s;
// 線程1:
int a = s.a;
int b = s.b;
// 線程2:
s.a ++ ;
s.b -- ;
如果實際的執(zhí)行順序就是上述書寫的順序那到?jīng)]有什么,但如果線程2的執(zhí)行打斷了線程1,變?yōu)槿缦马樞颍?BR> int a = s.a; //線程1
s.a++; //線程2
s.b++; //線程2
int b = s.b; //線程1
那么這時線程1讀出來的a和b就會有問題了,因為a是在修改前讀的,而b是在修改后讀的,這樣讀出來的是不完整的數(shù)據(jù),會對程序帶來不可預(yù)料的后果。天知道兩個程的調(diào)度順序是什么樣的。為了防止這種情況的出現(xiàn),需要對變量s加鎖,也就是當線程1得到鎖以后就可以放心的訪問s,這時如果線程2要修改s,只有等線程1訪問完成以后將鎖釋放才可以,從而保證了上述兩線程交叉訪問變量的情況不會出現(xiàn)。
使用Win32提供的臨界區(qū)可以方便的實現(xiàn)這種鎖:
// 全局:
CRITICAL_SECTION cs;
InitializeCriticalSection( & cs);
// 線程1:
EnterCriticalSection( & cs);
int a = s.a;
int b = s.b;
LeaveCriticalSection( & cs);
// 線程2:
EnterCriticalSection( & cs);
s.a ++ ;
s.b -- ;
LeaveCriticalSection( & cs);
// 最后:
DeleteCriticalSection( & cs);
代碼中的臨界區(qū)變量(cs)就可以看作是變量s的鎖,當函數(shù)EnterCriticalSection返回時,當前線程就獲得了這把鎖,之后就是對變量的訪問了。訪問完成后,調(diào)用LeaveCriticalSection表示釋放這把鎖,允許其他線程繼續(xù)使用它。
這里我要說的是兩種用于C++的多線程同步類,通過對這兩種類的使用就可以方便的實現(xiàn)對變量或代碼段的加鎖控制,從而防止多線程對變量不正確的操作。
所謂加鎖,就是說當我們要訪問某關(guān)鍵變量之前,都需要首先獲得允許才能繼續(xù),如果未獲得允許則只有等待。一個關(guān)鍵變量擁有一把鎖,一個線程必須先得到這把鎖(其實稱為鑰匙可能更形象)才可以訪問這個變量,而當某個變量持有這把鎖的時候,其他線程就不能重復(fù)的得到它,只有等持有鎖的線程把鎖歸還以后其他線程才有可能得到它。之所以這樣做,就是為了防止一個線程讀取某對象途中另一線程對它進行了修改,或兩線程同時對一變量進行修改,例如:
// 全局:
struct MyStruct ... { int a, b; } ;
MyStruct s;
// 線程1:
int a = s.a;
int b = s.b;
// 線程2:
s.a ++ ;
s.b -- ;
如果實際的執(zhí)行順序就是上述書寫的順序那到?jīng)]有什么,但如果線程2的執(zhí)行打斷了線程1,變?yōu)槿缦马樞颍?BR> int a = s.a; //線程1
s.a++; //線程2
s.b++; //線程2
int b = s.b; //線程1
那么這時線程1讀出來的a和b就會有問題了,因為a是在修改前讀的,而b是在修改后讀的,這樣讀出來的是不完整的數(shù)據(jù),會對程序帶來不可預(yù)料的后果。天知道兩個程的調(diào)度順序是什么樣的。為了防止這種情況的出現(xiàn),需要對變量s加鎖,也就是當線程1得到鎖以后就可以放心的訪問s,這時如果線程2要修改s,只有等線程1訪問完成以后將鎖釋放才可以,從而保證了上述兩線程交叉訪問變量的情況不會出現(xiàn)。
使用Win32提供的臨界區(qū)可以方便的實現(xiàn)這種鎖:
// 全局:
CRITICAL_SECTION cs;
InitializeCriticalSection( & cs);
// 線程1:
EnterCriticalSection( & cs);
int a = s.a;
int b = s.b;
LeaveCriticalSection( & cs);
// 線程2:
EnterCriticalSection( & cs);
s.a ++ ;
s.b -- ;
LeaveCriticalSection( & cs);
// 最后:
DeleteCriticalSection( & cs);
代碼中的臨界區(qū)變量(cs)就可以看作是變量s的鎖,當函數(shù)EnterCriticalSection返回時,當前線程就獲得了這把鎖,之后就是對變量的訪問了。訪問完成后,調(diào)用LeaveCriticalSection表示釋放這把鎖,允許其他線程繼續(xù)使用它。

