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

字號:

上一篇中我介紹了一種通過封閉Critical Section對象而方便的使用互斥鎖的方式,文中所有的例子是兩個線程對同一數(shù)據(jù)一讀一寫,因此需要讓它們在這里互斥,不能同時訪問。而在實際情況中可能會有更復(fù)雜的情況出現(xiàn),就是多個線程訪問同一數(shù)據(jù),一部分是讀,一部分是寫。我們知道只有讀-寫或?qū)?寫同時進行時可能會出現(xiàn)問題,而讀-讀則可以同時進行,因為它們不會對數(shù)據(jù)進行修改,所以也有必要在C++中封裝一種方便的允許讀-讀并發(fā)、讀-寫與寫-寫互斥的鎖。要實現(xiàn)這種鎖,使用臨界區(qū)就很困難了,不如改用內(nèi)核對象,這里我使用的是互斥量(Mutex)。
    總體的結(jié)構(gòu)與上一篇中的類似,都是寫出一個對鎖進行封裝的基類,再寫一個用于調(diào)用加、解鎖函數(shù)的類,通過對第二個類的生命周期的管理實現(xiàn)加鎖和解鎖。這里涉及到兩個新問題,一是加鎖、解鎖動作都有兩種,一種是加/解讀鎖,一種是加/解寫鎖;二是為了允許讀-讀并發(fā),這里只聲明一個Mutex是不夠的,必須要聲明多個Mutex,而且有多少個Mutex就同時允許多少個讀線程并發(fā),之所以這么說,是因為我們要使用的API函數(shù)是WaitForMultipleObjects。
    WaitForMultipleObjects函數(shù)的功能就是等待對象狀態(tài)被設(shè)置,MSDN中對它的說明為:
    Waits until one or all of the specified objects are in the signaled state or the time-out interval elapses.
    這是個很好用的函數(shù),我們可以用它來等待某個或某幾個對象,并且允許設(shè)置超時時間,等待成功時與超時時返回的值是不同的。如果返回的值比WAIT_ABANDONED小則表示等待成功?!暗却晒Α睂τ诓煌愋偷膬?nèi)核對象有不同的意義,例如對于進程或線程對象,等待成功就表示進程或線程執(zhí)行結(jié)束了;對于互斥量對象,則表示此對象現(xiàn)在不被任何其他線程擁有,并且一旦等待成功,當(dāng)前線程即擁有了此互斥量,其他線程則不能同時擁有,直接調(diào)用ReleaseMutex函數(shù)主動釋放互斥量。
    與WaitForMultipleObjects類似的還有一個函數(shù)WaitForSingleObject,它的功能比較簡單,只針對單一個對象,而WaitForMultipleObjects可以同時等待多個對象,并且可以設(shè)置是否等待所有對象。
    上一篇文章中用的InstanceLockBase類里面封裝了一個Critical Section對象,這里則要封裝一組Mutex的Handle,那么這一組是多少個呢?它應(yī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) ...{}
    …
    };
    這確實是個不錯的辦法,通過在子類構(gòu)造函數(shù)的初始化段中調(diào)用基類構(gòu)造函數(shù)并傳參,使得這個動態(tài)數(shù)組得以正確初始化,不過這樣看著不太爽,子類必須兩次出現(xiàn)“RWLockBase”一詞,能不能像InstanceLockBase那樣只要繼承了就好呢?答案是肯定的,只要用C++模板即可:
    template
    class RWLockBase
    ...{
    HANDLE handles[maxReadCount];
    …
    };
    使用模板附帶這么一個好處,因為模板參數(shù)是在編譯期可以確定的,所以無需再用動態(tài)數(shù)組,直接在棧上分配即可。而使用模板引出一個新問題,就是相應(yīng)的Lock類(RWLock)在構(gòu)造時傳的對象指針時的類型聲明,直接寫成RWLock(RWLockBase* pObj)肯定是不行的,因為必須指定模板參數(shù),并且其值還必須與聲明RWLockBase時所指定的值一致才行,從而客戶端代碼就必須兩次指定模板參數(shù)值,不爽!解決的辦法也是有一個,就是把RWLockBase變成夾層類,為它再聲明一個基類,讓RWLock接收的是基類指針,并把Lock、Unlock等函數(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;
    };