上一篇中我介紹了一種通過(guò)封閉Critical Section對(duì)象而方便的使用互斥鎖的方式,文中所有的例子是兩個(gè)線程對(duì)同一數(shù)據(jù)一讀一寫,因此需要讓它們?cè)谶@里互斥,不能同時(shí)訪問(wèn)。而在實(shí)際情況中可能會(huì)有更復(fù)雜的情況出現(xiàn),就是多個(gè)線程訪問(wèn)同一數(shù)據(jù),一部分是讀,一部分是寫。我們知道只有讀-寫或?qū)?寫同時(shí)進(jìn)行時(shí)可能會(huì)出現(xiàn)問(wèn)題,而讀-讀則可以同時(shí)進(jìn)行,因?yàn)樗鼈儾粫?huì)對(duì)數(shù)據(jù)進(jìn)行修改,所以也有必要在C++中封裝一種方便的允許讀-讀并發(fā)、讀-寫與寫-寫互斥的鎖。要實(shí)現(xiàn)這種鎖,使用臨界區(qū)就很困難了,不如改用內(nèi)核對(duì)象,這里我使用的是互斥量(Mutex)。
總體的結(jié)構(gòu)與上一篇中的類似,都是寫出一個(gè)對(duì)鎖進(jìn)行封裝的基類,再寫一個(gè)用于調(diào)用加、解鎖函數(shù)的類,通過(guò)對(duì)第二個(gè)類的生命周期的管理實(shí)現(xiàn)加鎖和解鎖。這里涉及到兩個(gè)新問(wèn)題,一是加鎖、解鎖動(dòng)作都有兩種,一種是加/解讀鎖,一種是加/解寫鎖;二是為了允許讀-讀并發(fā),這里只聲明一個(gè)Mutex是不夠的,必須要聲明多個(gè)Mutex,而且有多少個(gè)Mutex就同時(shí)允許多少個(gè)讀線程并發(fā),之所以這么說(shuō),是因?yàn)槲覀円褂玫腁PI函數(shù)是WaitForMultipleObjects。
WaitForMultipleObjects函數(shù)的功能就是等待對(duì)象狀態(tài)被設(shè)置,MSDN中對(duì)它的說(shuō)明為:
Waits until one or all of the specified objects are in the signaled state or the time-out interval elapses.
這是個(gè)很好用的函數(shù),我們可以用它來(lái)等待某個(gè)或某幾個(gè)對(duì)象,并且允許設(shè)置超時(shí)時(shí)間,等待成功時(shí)與超時(shí)時(shí)返回的值是不同的。如果返回的值比WAIT_ABANDONED小則表示等待成功。“等待成功”對(duì)于不同類型的內(nèi)核對(duì)象有不同的意義,例如對(duì)于進(jìn)程或線程對(duì)象,等待成功就表示進(jìn)程或線程執(zhí)行結(jié)束了;對(duì)于互斥量對(duì)象,則表示此對(duì)象現(xiàn)在不被任何其他線程擁有,并且一旦等待成功,當(dāng)前線程即擁有了此互斥量,其他線程則不能同時(shí)擁有,直接調(diào)用ReleaseMutex函數(shù)主動(dòng)釋放互斥量。
與WaitForMultipleObjects類似的還有一個(gè)函數(shù)WaitForSingleObject,它的功能比較簡(jiǎn)單,只針對(duì)單一個(gè)對(duì)象,而WaitForMultipleObjects可以同時(shí)等待多個(gè)對(duì)象,并且可以設(shè)置是否等待所有對(duì)象。
上一篇文章中用的InstanceLockBase類里面封裝了一個(gè)Critical Section對(duì)象,這里則要封裝一組Mutex的Handle,那么這一組是多少個(gè)呢?它應(yīng)該由使用此類的程序中定義,例如可以用動(dòng)態(tài)數(shù)組的方法:
//
基類:
???????
class
?RWLockBase?
//
表示Read/Write?Lock
???????
...
{
??????????????HANDLE
*
?handles;
???????
protected
:

??????????????RWLockBase(
int
?handleCount)?
...
{?handles?
=
?
new
?HANDLE[handleCount];?}
??????????????…
???????}
;
//
子類:
???????
class
?MyClass:?
public
?RWLockBase??????

???????
...
{

??????????????MyClass():?RWLockBase(
3
)?
...
{}
??????????????…
???????}
;
這確實(shí)是個(gè)不錯(cuò)的辦法,通過(guò)在子類構(gòu)造函數(shù)的初始化段中調(diào)用基類構(gòu)造函數(shù)并傳參,使得這個(gè)動(dòng)態(tài)數(shù)組得以正確初始化,不過(guò)這樣看著不太爽,子類必須兩次出現(xiàn)“RWLockBase”一詞,能不能像InstanceLockBase那樣只要繼承了就好呢?答案是肯定的,只要用C++模板即可:
class
?_RWLockBase

...
{
???????friend?
class
?RWLock;
protected
:
???????
virtual
?DWORD?ReadLock(
int
?timeout)?
=
?
0
;
???????
virtual
?
void
?ReadUnlock(
int
?handleIndex)?
=
?
0
;
???????
virtual
?DWORD?WriteLock(
int
?timeout)?
=
?
0
;
???????
virtual
?
void
?WriteUnlock()?
=
?
0
;
}
;
模板類RWLockBase從_RWLockBase繼承,并對(duì)四個(gè)函數(shù)寫出實(shí)現(xiàn):
template?
<
int
?maxReadCount?
=
?
3
>
????????
//
這里給一個(gè)缺省參數(shù),盡量減少客戶端代碼量
class
?RWLockBase:?
public
?_RWLockBase

...
{
???????HANDLE?handles[maxReadCount];
???????DWORD?ReadLock(
int
?timeout)???
//
加讀鎖,只要等到一個(gè)互斥量返回即可
???????
...
{
??????????????
return
?::WaitForMultipleObjects(maxReadCount,?handles,?FALSE,?timeout);
???????}
???????
void
?ReadUnlock(
int
?handleIndex)?
//
解讀鎖,釋放已獲得的互斥量
???????
...
{
??????????????::ReleaseMutex(handles[handleIndex]);
???????}
???????DWORD?WriteLock(
int
?timeout)???
//
加寫鎖,等到所有互斥量,從而與其他所有線程互斥
???????
...
{
??????????????
return
?::WaitForMultipleObjects(maxReadCount,?handles,?TRUE,?timeout);
???????}
???????
void
?WriteUnlock()??????????????????????
//
解寫鎖,釋放所有的互斥量
???????
...
{
??????????????
for
(
int
?i?
=
?
0
;?i?
<
?maxReadCount;?i
++
)
?????????????????????::ReleaseMutex(handles[i]);
???????}
protected
:
???????RWLockBase()????????????????????????????
//
構(gòu)造函數(shù),初始化每個(gè)互斥量
???????
...
{
??????????????
for
(
int
?i?
=
?
0
;?i?
<
?maxReadCount;?i
++
)
?????????????????????handles[i]?
=
?::CreateMutex(
0
,?FALSE,?
0
);
???????}
???????
~
RWLockBase()??????????????????????????
//
析構(gòu)函數(shù),銷毀對(duì)象
???????
...
{
??????????????
for
(
int
?i?
=
?
0
;?i?
<
?maxReadCount;?i
++
)
?????????????????????::CloseHandle(handles[i]);
???????}
}
;
而相應(yīng)的鎖類也會(huì)稍復(fù)雜一些:
class
?RWLock

...
{
???????
bool
?lockSuccess;????????????????
//
因?yàn)橛锌赡艹瑫r(shí),需要保存是否等待成功
???????
int
?readLockHandleIndex;??????
//
對(duì)于讀鎖,需要知道獲得的是哪個(gè)互斥量
???????_RWLockBase
*
?_pObj;?????????
//
目標(biāo)對(duì)象基類指針
public
:
???????
//
這里通過(guò)第二個(gè)參數(shù)決定是加讀鎖還是寫鎖,第三個(gè)參數(shù)為超時(shí)的時(shí)間
???????RWLock(_RWLockBase
*
?pObj,?
bool
?readLock?
=
?
true
,?
int
?timeout?
=
?
3000
)

???????
...
{
??????????????_pObj?
=
?pObj;
??????????????lockSuccess?
=
?FALSE;
??????????????readLockHandleIndex?
=
?
-
1
;
??????????????
if
(NULL?
==
?_pObj)
?????????????????????
return
;
?
??????????????
if
(readLock)??????????
//
讀鎖
??????????????
...
{
?????????????????????DWORD?retval?
=
?_pObj
->
ReadLock(timeout);
?????????????????????
if
(retval?
<
?WAIT_ABANDONED)?
//
返回值小于WAIT_ABANDONED表示成功
?????????????????????
...
{???????????????????????????????????????????????
//
其值減WAIT_OBJECT_0就是數(shù)組下標(biāo)
????????????????????????????readLockHandleIndex?
=
?retval?
-
?WAIT_OBJECT_0;
????????????????????????????lockSuccess?
=
?TRUE;
?????????????????????}
??????????????}
??????????????
else
??????????????
...
{
?????????????????????DWORD?retval?
=
?_pObj
->
WriteLock(timeout);
?????????????????????
if
(retval?
<
?WAIT_ABANDONED)?
//
寫鎖時(shí)獲得了所有互斥量,無(wú)需保存下標(biāo)
????????????????????????????lockSuccess?
=
?TRUE;
??????????????}
???????}
???????
~
RWLock()

???????
...
{
??????????????
if
(NULL?
==
?_pObj)
?????????????????????
return
;
??????????????
if
(readLockHandleIndex?
>
?
-
1
)
?????????????????????_pObj
->
ReadUnlock(readLockHandleIndex);
??????????????
else
?????????????????????_pObj
->
WriteUnlock();
???????}
???????
bool
?IsLockSuccess()?
const
?
...
{?
return
?lockSuccess;?}
}
;
?
這樣一來(lái),讀/寫鎖的類也就完成了,使用時(shí)與InstanceLock類似:
1 被鎖對(duì)象從RWLockBase<>類繼承
2 需要加讀鎖時(shí),聲明一個(gè)RWLock實(shí)例,并指出要加的是讀鎖
3 需要加寫鎖時(shí),聲明一個(gè)RWLock實(shí)例,并指出要加的是寫鎖
?
這里還是要多說(shuō)兩句,雖然使用純虛函數(shù)結(jié)合模板類,使得客戶端代碼量減到最少,但性能上有一些影響,因?yàn)槁暶髁颂摵瘮?shù),則實(shí)例中必然存在4個(gè)字節(jié)的VPTR,調(diào)用虛函數(shù)時(shí)則要查找VTABLE,空間和時(shí)間上都有微小的犧牲。而如果不使用模板類,則沒有虛函數(shù)的代價(jià),但也有犧牲:不使用模板類則需要使用動(dòng)態(tài)數(shù)組,動(dòng)態(tài)數(shù)組本身需要程序運(yùn)行時(shí)在堆上分配,這也需要時(shí)間;指向動(dòng)態(tài)數(shù)組的指針也需要占用內(nèi)存,所以空間上的開鎖是一樣的,時(shí)間上雖然動(dòng)態(tài)分配內(nèi)存需要的時(shí)間應(yīng)該比虛函數(shù)的調(diào)用要慢一點(diǎn),但初始化只需要一次,總體來(lái)說(shuō)也是值得的。所以最終要使用哪一種,就看具體需要了。
?
這里也給出一個(gè)實(shí)驗(yàn)。這里所用的被鎖類也上一篇類似,簡(jiǎn)單的從RWLockBase類繼承:
class
?MyClass2:?
public
?RWLockBase
<>
...
{}
;
MyClass2?mc2;
看看兩個(gè)線程函數(shù):
//
讀線程
DWORD?CALLBACK?ReadThreadProc(LPVOID?param)

...
{
???????
int
?i?
=
?(
int
)param;
???????RWLock?
lock
(
&
mc2);??????????
//
加讀鎖
???????
if
(
lock
.IsLockSuccess())?????????????
//
如果加鎖成功
???????
...
{
??????????????Say(
"
read?thread?%d?started
"
,?i);???
//
為了代碼短一些,假設(shè)Say函數(shù)有這種能力
??????????????Sleep(
1000
);
??????????????Say(
"
read?thread?%d?ended
"
,?i);
???????}
???????
else
?????????????????????????????????????
//
加鎖超時(shí),則顯示超時(shí)信息
??????????????Say(
"
read?thread?%d?timeout
"
,?i);
???????
return
?
0
;
}
//
寫線程
DWORD?CALLBACK?WriteThreadProc(LPVOID?param)

...
{
???????
int
?i?
=
?(
int
)param;
???????RWLock?
lock
(
&
mc2,?
false
);?
//
加寫鎖。
???????
if
(
lock
.IsLockSuccess())

???????
...
{
??????????????Say(
"
write?thread?%d?started
"
,?i);
??????????????Sleep(
600
);
??????????????Say(
"
write?thread?%d?ended
"
,?i);
???????}
???????
else
??????????????Say(
"
write?thread?%d?timeout
"
,?i);
???????
return
?
0
;
}
?
主線程:
???????
int
?i;
???????
for
(i?
=
?
0
;?i?
<
?
5
;?i
++
)
??????????????::CreateThread(
0
,?
0
,?ReadThreadProc,?(LPVOID)i,?
0
,?
0
);
???????
for
(i?
=
?
0
;?i?
<
?
5
;?i
++
)
??????????????::CreateThread(
0
,?
0
,?WriteThreadProc,?(LPVOID)i,?
0
,?
0
);
程序共開10個(gè)線程,5個(gè)讀5個(gè)寫。從RWLockBase類繼承時(shí)我們使用了默認(rèn)的模板參數(shù),所以最多同時(shí)允許3個(gè)讀線程。程序的運(yùn)行結(jié)果如下:
001 [15:07:28.484]read thread 0 started
002 [15:07:28.484]read thread 1 started
003 [15:07:28.484]read thread 2 started
004 [15:07:29.484]read thread 0 ended
005 [15:07:29.484]read thread 3 started
006 [15:07:29.484]read thread 1 ended
007 [15:07:29.484]read thread 4 started
008 [15:07:29.484]read thread 2 ended
009 [15:07:30.484]read thread 3 ended
010 [15:07:30.484]read thread 4 ended
011 [15:07:30.484]write thread 0 started
012 [15:07:31.078]write thread 0 ended
013 [15:07:31.078]write thread 1 started
014 [15:07:31.484]write thread 2 timeout
015 [15:07:31.484]write thread 3 timeout
016 [15:07:31.484]write thread 4 timeout
017 [15:07:31.687]write thread 1 ended
前三行三個(gè)讀線程取得讀鎖,之后等一秒(第4-8行),三個(gè)讀線程都結(jié)束了,并且余下的兩個(gè)讀線程取得讀鎖,雖然這時(shí)剩下了一個(gè)互斥量沒有使用,但因?yàn)槠渌木€程都請(qǐng)求加寫鎖,寫鎖與其他所有線程互斥,所以還不能取得寫鎖。再過(guò)一秒(第9-11行),后來(lái)的兩個(gè)取得讀鎖的線程也結(jié)束了,則第一個(gè)寫線程取得寫鎖。600毫秒之后(第12-13行)第一個(gè)寫線程結(jié)束,第二個(gè)寫線程開始。400毫秒之后(第14-16行)余下的三個(gè)寫線程都超時(shí)了,再后第二個(gè)寫線程也結(jié)束了。
posted on 2006-10-19 14:35
Jerry Cat 閱讀(1503)
評(píng)論(1) 編輯 收藏 引用