Win32下兩種用于C++的線程同步類(下)

字號(hào):

上一篇中我介紹了一種通過封閉Critical Section對(duì)象而方便的使用互斥鎖的方式,文中所有的例子是兩個(gè)線程對(duì)同一數(shù)據(jù)一讀一寫,因此需要讓它們?cè)谶@里互斥,不能同時(shí)訪問。而在實(shí)際情況中可能會(huì)有更復(fù)雜的情況出現(xiàn),就是多個(gè)線程訪問同一數(shù)據(jù),一部分是讀,一部分是寫。我們知道只有讀-寫或?qū)?寫同時(shí)進(jìn)行時(shí)可能會(huì)出現(xià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ù)的類,通過對(duì)第二個(gè)類的生命周期的管理實(shí)現(xiàn)加鎖和解鎖。這里涉及到兩個(gè)新問題,一是加鎖、解鎖動(dòng)作都有兩種,一種是加/解讀鎖,一種是加/解寫鎖;二是為了允許讀-讀并發(fā),這里只聲明一個(gè)Mutex是不夠的,必須要聲明多個(gè)Mutex,而且有多少個(gè)Mutex就同時(shí)允許多少個(gè)讀線程并發(fā),之所以這么說,是因?yàn)槲覀円褂玫腁PI函數(shù)是WaitForMultipleObjects。
    WaitForMultipleObjects函數(shù)的功能就是等待對(duì)象狀態(tài)被設(shè)置,MSDN中對(duì)它的說明為:
    Waits until one or all of the specified objects are in the signaled state or the time-out interval elapses.
    這是個(gè)很好用的函數(shù),我們可以用它來等待某個(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ò)的辦法,通過在子類構(gòu)造函數(shù)的初始化段中調(diào)用基類構(gòu)造函數(shù)并傳參,使得這個(gè)動(dòng)態(tài)數(shù)組得以正確初始化,不過這樣看著不太爽,子類必須兩次出現(xiàn)“RWLockBase”一詞,能不能像InstanceLockBase那樣只要繼承了就好呢?答案是肯定的,只要用C++模板即可:
    template
    class RWLockBase
    ...{
    HANDLE handles[maxReadCount];
    …
    };
    使用模板附帶這么一個(gè)好處,因?yàn)槟0鍏?shù)是在編譯期可以確定的,所以無需再用動(dòng)態(tài)數(shù)組,直接在棧上分配即可。而使用模板引出一個(gè)新問題,就是相應(yīng)的Lock類(RWLock)在構(gòu)造時(shí)傳的對(duì)象指針時(shí)的類型聲明,直接寫成RWLock(RWLockBase* pObj)肯定是不行的,因?yàn)楸仨氈付0鍏?shù),并且其值還必須與聲明RWLockBase時(shí)所指定的值一致才行,從而客戶端代碼就必須兩次指定模板參數(shù)值,不爽!解決的辦法也是有一個(gè),就是把RWLockBase變成夾層類,為它再聲明一個(gè)基類,讓RWLock接收的是基類指針,并把Lock、Unlock等函數(shù)放在基類中,聲明為純虛函數(shù),實(shí)現(xiàn)寫在夾層類中:
    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;
    };