線程同步是多線程程序設(shè)計(jì)的核心內(nèi)容,它的目的是正確處理多線程并發(fā)時(shí)的各種問題,例如線程的等待、多個(gè)線程訪問同一數(shù)據(jù)時(shí)的互斥,防死鎖等。Win32提供多種內(nèi)核對(duì)象和手段用于線程同步,如互斥量、信號(hào)量、事件、臨界區(qū)等。所不同的是,互斥量、信號(hào)量、事件都是Windows的內(nèi)核對(duì)象,當(dāng)程序?qū)@些對(duì)象進(jìn)行控制時(shí)會(huì)自動(dòng)轉(zhuǎn)換到核心態(tài),而臨界區(qū)本身不是內(nèi)核對(duì)象,它是工作在用戶態(tài)的。我們知道從用戶態(tài)轉(zhuǎn)換到核心態(tài)是需要以時(shí)間為代價(jià)的,所以如果能在用戶態(tài)就簡單解決的問題,就可以不必勞煩核心態(tài)了。
這里我要說的是兩種用于C++的多線程同步類,通過對(duì)這兩種類的使用就可以方便的實(shí)現(xiàn)對(duì)變量或代碼段的加鎖控制,從而防止多線程對(duì)變量不正確的操作。
所謂加鎖,就是說當(dāng)我們要訪問某關(guān)鍵變量之前,都需要首先獲得允許才能繼續(xù),如果未獲得允許則只有等待。一個(gè)關(guān)鍵變量擁有一把鎖,一個(gè)線程必須先得到這把鎖(其實(shí)稱為鑰匙可能更形象)才可以訪問這個(gè)變量,而當(dāng)某個(gè)變量持有這把鎖的時(shí)候,其他線程就不能重復(fù)的得到它,只有等持有鎖的線程把鎖歸還以后其他線程才有可能得到它。之所以這樣做,就是為了防止一個(gè)線程讀取某對(duì)象途中另一線程對(duì)它進(jìn)行了修改,或兩線程同時(shí)對(duì)一變量進(jìn)行修改,例如:
//
全局:
???????
struct
?MyStruct?
...
{?
int
?a,?b;?}
;
???????MyStruct?s;
//
線程1:
???????
int
?a?
=
?s.a;
???????
int
?b?
=
?s.b;
//
線程2:
???????s.a
++
;
???????s.b
--
;
如果實(shí)際的執(zhí)行順序就是上述書寫的順序那到?jīng)]有什么,但如果線程2的執(zhí)行打斷了線程1,變?yōu)槿缦马樞颍?/font>
?????? int a = s.a;????? //線程1
?????? s.a++;??????????? //線程2
?????? s.b++;??????????? //線程2
?????? int b = s.b;????? //線程1
那么這時(shí)線程1讀出來的a和b就會(huì)有問題了,因?yàn)閍是在修改前讀的,而b是在修改后讀的,這樣讀出來的是不完整的數(shù)據(jù),會(huì)對(duì)程序帶來不可預(yù)料的后果。天知道兩個(gè)程的調(diào)度順序是什么樣的。為了防止這種情況的出現(xiàn),需要對(duì)變量s加鎖,也就是當(dāng)線程1得到鎖以后就可以放心的訪問s,這時(shí)如果線程2要修改s,只有等線程1訪問完成以后將鎖釋放才可以,從而保證了上述兩線程交叉訪問變量的情況不會(huì)出現(xiàn)。
使用Win32提供的臨界區(qū)可以方便的實(shí)現(xiàn)這種鎖:
如果每當(dāng)需要對(duì)一個(gè)變量進(jìn)行加鎖時(shí)都需要做這些操作,顯得有些麻煩,而且變量cs與s只有邏輯上的鎖關(guān)系,在語法上沒有什么聯(lián)系,這對(duì)于鎖的管理帶來了不小的麻煩。程序員總是最懶的,可以想出各種偷懶的辦法來解決問題,例如讓被鎖的變量與加鎖的變量形成物理上的聯(lián)系,使得鎖變量成為被鎖變量不可分割的一部分,這聽起來是個(gè)好主意。
首先想到的是把鎖封閉在一個(gè)類里,讓類的構(gòu)造函數(shù)和析構(gòu)函數(shù)來管理對(duì)鎖的初始化和鎖毀動(dòng)作,我們稱這個(gè)鎖為“實(shí)例鎖”:
???????
struct
?MyStruct:?
public
?InstanceLockBase

???????
...
{?…?}
;
什么?結(jié)構(gòu)體還能從類繼承?當(dāng)然,C++中結(jié)構(gòu)體和類除了成員的默認(rèn)訪問控制不同外沒有什么不一樣,class能做的struct也能做。此外,也許你還會(huì)問,如果被鎖的是個(gè)簡單類型,不能繼承怎么辦,那么要么用一個(gè)類對(duì)這個(gè)簡單類型進(jìn)行封裝(記得Java里有int和Integer嗎),要么只好手工管理它們的聯(lián)系了。如果被鎖類已經(jīng)有了基類呢?沒關(guān)系,C++是允許多繼承的,多一個(gè)基類也沒什么。
現(xiàn)在我們的數(shù)據(jù)里面已經(jīng)包含一把鎖了,之后就是要添加加鎖和解鎖的動(dòng)作,把它們作為InstanceLockBase類的成員函數(shù)再合適不過了:
???????
class
?InstanceLockBase

???????
...
{
??????????????CRITICAL_SECTION?cs;

??????????????
void
?Lock()?
...
{?EnterCriticalSection(
&
cs);?}
??????????????
void
?Unlock()?
...
{?LeaveCriticalSection(
&
cs);?}
??????????????…
}
;
看到這里可能會(huì)發(fā)現(xiàn),我把Lock和Unlock函數(shù)都聲明為私有了,那么如何訪問這兩個(gè)函數(shù)呢?是的,我們總是需要有一個(gè)地方來調(diào)用這兩個(gè)函數(shù)以實(shí)現(xiàn)加鎖和解鎖的,而且它們總應(yīng)該成對(duì)出現(xiàn),但C++語法本身沒能限制我們必須成對(duì)的調(diào)用兩個(gè)函數(shù),如果加完鎖忘了解,那后果是嚴(yán)重的。這里有一個(gè)例外,就是C++對(duì)于構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用是自動(dòng)成對(duì)的,對(duì)了,那就把對(duì)Lock和Unlock的調(diào)用專門寫在一個(gè)類的構(gòu)造函數(shù)和析構(gòu)函數(shù)中:
class
?InstanceLock

...
{
??????????????InstanceLockBase
*
?_pObj;
public
:
??????????????InstanceLock(InstanceLockBase
*
?pObj)

??????????????
...
{
?????????????????????_pObj?
=
?pObj;?
//
這里會(huì)保存一份指向s的指針,用于解鎖
?????????????????????
if
(NULL?
!=
?_pObj)
????????????????????????????_pObj
->
Lock();??????
//
這里加鎖
??????????????}
??????????????
~
InstanceLock()

??????????????
...
{
?????????????????????
if
(NULL?
!=
?_pObj)
????????????????????????????_pObj
->
Unlock();???
//
這里解鎖
??????????????}
}
;
最后別忘了在類InstanceLockBase中把InstanceLock聲明為友元,使得它能正確訪問Lock和Unlock這兩個(gè)私有函數(shù):
class
?InstanceLockBase

...
{
??????????????friend?
class
?InstanceLock;
??????????????…
}
;
好了,有了上面的基礎(chǔ),現(xiàn)在對(duì)變量s的加解鎖管理變成了對(duì)InstanceLock的實(shí)例的生命周期的管理了。假如我們有一個(gè)函數(shù)ModifyS中要對(duì)s進(jìn)行修改,那么只要在函數(shù)一開始就聲明一個(gè)InstaceLock的實(shí)例,這樣整個(gè)函數(shù)就自動(dòng)對(duì)s加鎖,一旦進(jìn)入這個(gè)函數(shù),其他線程就都不能獲得s的鎖了:
???????
void
?ModifyS()

???????
...
{
??????????????InstanceLock?
lock
(
&
s);????????
//
這里已經(jīng)實(shí)現(xiàn)加鎖了
??????????????
//
some?operations?on?s
???????}
????
?????????????????????????????????????????? //
一旦離開lock對(duì)象的作用域,自動(dòng)解鎖
如果是要對(duì)某函數(shù)中一部分代碼加鎖,只要用一對(duì)大括號(hào)把它們括起來再聲明一個(gè)lock就可以了:
首先準(zhǔn)備一個(gè)輸出函數(shù),對(duì)我們理解程序有幫助。它會(huì)在輸出我們想輸出的內(nèi)容同時(shí)打出行號(hào)和時(shí)間:
void
?Say(
char
*
?text)

...
{
??????????????
static
?
int
?count?
=
?
0
;
??????????????SYSTEMTIME?st;
??????????????::GetLocalTime(
&
st);
??????????????printf(
"
%03d?[%02d:%02d:%02d.%03d]%s
"
,?
++
count,?st.wHour,?st.wMinute,?st.wSecond,?st.wMilliseconds,?text);
}
當(dāng)然,原則上當(dāng)多線程都調(diào)用這個(gè)函數(shù)時(shí)應(yīng)該對(duì)其靜態(tài)局部變量count進(jìn)行加鎖,這里就省略了。
我們聲明一個(gè)非常簡單的被鎖的類型,并生成一個(gè)實(shí)例:
class
?MyClass:?
public
?InstanceLockBase

...
{}
;
MyClass?mc;
子線程的任務(wù)就是對(duì)這個(gè)對(duì)象加鎖,然后輸出一些信息:
DWORD?CALLBACK?ThreadProc(LPVOID?param)

...
{
??????????????InstanceLock?il(
&
mc);
??????????????Say(
"
in?sub?thread,?lock
"
);
??????????????Sleep(
2000
);
??????????????Say(
"
in?sub?thread,?unlock
"
);
??????????????
return
?
0
;
}
這里會(huì)輸出兩條信息,一是在剛剛獲得鎖的時(shí)間,二是在釋放鎖的時(shí)候,中間通過Sleep來延遲2秒。
主線程負(fù)責(zé)開啟子線程,然后也對(duì)mc加鎖:
001 [13:43:23.781]in main thread, lock
002 [13:43:26.781]in main thread, lock
003 [13:43:26.781]in sub thread, lock
004 [13:43:28.781]in sub thread, unlock
從其輸出的行號(hào)和時(shí)間可以清楚的看到兩個(gè)線程間的互斥:當(dāng)主線程恰好首先獲得鎖時(shí),它會(huì)延遲3秒,然后釋放鎖,之后子線程才得以繼續(xù)進(jìn)行。這個(gè)例子也證明我們的類工作的很好。
總結(jié)一下,要使用InstanceLock系列類,要做的就是:
1 讓被鎖類從InstanceLockBase繼承
2 所有要訪問被鎖對(duì)象的代碼前面聲明InstanceLock的實(shí)例,并傳入被鎖對(duì)象的指針。
?
附:完整源代碼:
#pragma
?once
#include?
<
windows.h
>
?
class
?InstanceLock;
?
class
?InstanceLockBase

...
{
???????friend?
class
?InstanceLock;
?
???????CRITICAL_SECTION?cs;
?
???????
void
?Lock()

???????
...
{
??????????????::EnterCriticalSection(
&
cs);
???????}
?
???????
void
?Unlock()

???????
...
{
??????????????::LeaveCriticalSection(
&
cs);
???????}
?
protected
:
???????InstanceLockBase()

???????
...
{
??????????????::InitializeCriticalSection(
&
cs);
???????}
?
???????
~
InstanceLockBase()

???????
...
{
??????????????::DeleteCriticalSection(
&
cs);
???????}
}
;
?
class
?InstanceLock

...
{
???????InstanceLockBase
*
?_pObj;
public
:
???????InstanceLock(InstanceLockBase
*
?pObj)

???????
...
{
??????????????_pObj?
=
?pObj;
??????????????
if
(NULL?
!=
?_pObj)
?????????????????????_pObj
->
Lock();
???????}
?
???????
~
InstanceLock()

???????
...
{
??????????????
if
(NULL?
!=
?_pObj)
?????????????????????_pObj
->
Unlock();
???????}
}
;
posted on 2006-10-19 14:33
Jerry Cat 閱讀(1525)
評(píng)論(1) 編輯 收藏 引用