C++多線程同步技術(MFC)

1. 何時使用同步類

    MFC 提供的多線程類分爲兩類:同步對象(CSyncObject、CSemaphore、CMutex、CCriticalSection 和 CEvent)和同步訪問對象(CMultiLock 和 CSingleLock)。
當必須控制對資源的訪問以確保資源的完整性時,使用同步類。同步訪問類用於獲取對這些資源的訪問權。

    若要確定應使用的同步類,請詢問以下一系列問題:

    1)應用程序必須等到發生某事才能訪問資源(例如,在將數據寫入文件之前,必須先從通信端口接收它)嗎?
如果是,請使用 CEvent
    2)同一應用程序內一個以上的線程可以同時訪問此資源(例如,應用程序允許在同一文檔上最多同時打開五個帶有視圖的窗口)嗎?
如果是,請使用 CSemaphore
    3)可以有一個以上的應用程序使用此資源(例如,資源在 DLL 中)嗎?
如果是,請使用 CMutex
如果不是,請使用 CCriticalSection
    從不直接使用 CSyncObject。它是其他四個同步類的基類。

2. 何時使用同步訪問類

    如果應用程序只與訪問單個受控資源有關,請使用 CSingleLock
    如果需要訪問多個受控資源中的任何一個,則使用 CMultiLock

3. 如何使用同步類

    寫入多線程應用程序時,線程間的同步資源訪問是一個常見問題。兩個或多個線程同時訪問同一數據會導致不合需要的、不可預知的結果。例如,一個線程可能正在更新結構的內容,而另一個線程正在讀取同一結構的內容。無法得知讀取線程將會收到何種數據:舊數據、新寫入的數據或兩種數據都有。MFC 提供了多個同步類和同步訪問類以幫助解決此問題。
    典型的多線程應用程序具有代表各個線程間要共享的資源的類。正確設計的完全線程安全類不需要調用任何同步函數。該類的任何事情都在內部處理,使您可以將精力集中於如何更好地使用類,而不是它如何會損壞。創建完全線程安全類的有效技術是將同步類合併到資源類中。將同步類合併到共享類是一個簡單的過程。
    以維護鏈接的帳戶列表的應用程序爲例。此應用程序允許在獨立的窗口中最多檢查三個帳戶,但是在任何特定的時間,只能更新一個帳戶。更新帳戶後,通過網絡將更新的數據發送到數據存檔。
    此示例應用程序使用所有這三種類型的同步類。因爲它一次最多允許檢查三個帳戶,所以它使用 CSemaphore 限制對三個視圖對象的訪問。當試圖查看第四個帳戶時,應用程序或者等到前三個窗口中有一個關閉,或者該嘗試失敗。更新帳戶時,應用程序使用 CCriticalSection 確保一次只更新一個帳戶。更新成功後,發出信號 CEvent 以釋放等待該事件信號發送的線程。此線程將新數據發送到數據存檔。

4. 設計線程安全類

    若要使類完全線程安全,首先將適當的同步類作爲數據成員添加到共享類中。在前面的帳戶管理示例中,將 CSemaphore 數據成員添加到視圖類,將CCriticalSection 數據成員添加到鏈接的列表類,將 CEvent 數據成員添加到數據存儲類。
    下一步,將同步調用添加到修改類中的數據或訪問受控資源的所有成員函數中。應該在每個函數中創建 CSingleLock 或 CMultiLock 對象,並調用該對象的Lock 函數。當鎖定對象超出範圍並被銷燬時,該對象的析構函數調用 Unlock 以釋放資源。當然,如果願意,可直接調用Unlock
    用這種方式設計線程安全類使得在多線程應用程序中使用該類與使用非線程安全類一樣容易,但卻具有更高的安全級別。將同步對象和同步訪問權對象封裝到資源的類將提供完全線程安全編程的所有優點,而不會有維護同步代碼的缺點。
    下面的代碼示例通過使用在共享資源類和 CSingleLock 對象中聲明的數據成員 m_CritSectionCCriticalSection 類型),對此方法進行了說明。通過使用m_CritSection 對象的地址創建 CSingleLock 對象,來試圖同步共享資源(從 CWinThread 派生)。試圖鎖定資源,一旦鎖定,即完成了共享對象上的工作。完成工作後,即調用 Unlock 取消鎖定資源。

CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...

singleLock.Unlock();

此方法的缺點是此類將要比沒有添加同步對象的相同類慢一些。而且,如果有一個以上的線程可能刪除對象,合併方法不一定始終有效。在這種情況下,最好維持單獨同步對象。

5. CSemaphore

    一個CSemaphore類對象代表一個“信號”——一個同步對象,它允許有限數目的線程在一個或多個進程中訪問同一個資源。一個CSemaphore對象保持了對當前訪問某一指定資源的線程的計數。
    CSemaphore對象用於控制有限數量的用戶使用共享資源。它的當前計數是指還可以允許的其它用戶的數目。當這個計數達到零的時候,所有對這個由CSemaphore對象控制的資源的訪問嘗試都將被插入到一個系統隊列中等待,直到它們的時間用完或計數值不再爲零。這個被控制的資源可以同時接受訪問的最大用戶數目是在CSemaphore對象的構造期間被指定的。 
    要使用一個CSemaphore對象,則在需要的時候構造這個對象,指定你想要等待的信號的名字,應用程序應該在最初就擁有它。然後你就可以在構造函數返回時訪問這個信號。當你要訪問這個被控制的資源時,調用CSyncObject::Unlock。 
    使用CSemaphore對象的另一種方法是,將一個CSemaphore類型的變量添加到你想要控制的類中作爲一個數據成員。在被控制對象的構造期間,調用CSemaphore數據成員的構造函數來指定訪問計數的初始值,訪問計數的最大值,信號的名字(如果它要在整個進程中使用),以及需要的安全標誌。
    要訪問由CSemaphore對象用這種方式控制的資源,首先要在你的資源的訪問成員函數中創建一個CSingleLock類型或CMultiLock類型的變量。然後調用加鎖對象的Lock成員函數(例如,CSingleLock::Lock)。這時,你的線程將達到對資源的訪問,等待資源被釋放並訪問它,或者是在等待資源被釋放的過程中超過了時間,對資源的訪問失敗。不管是哪一種情況,你的資源都是以一種線程安全(thread-safe)方式被訪問的。要釋放資源,可以使用加鎖對象的Unlock成員函數(例如,CSingleLock::Unlock),或者是讓加鎖對象超越範圍,析構時釋放資源。
    另外,你可以單獨創建一個CSemaphore對象,並且在嘗試訪問被控制的資源之前顯式地訪問CSemaphore對象。這種方法雖然對閱讀你的源代碼的人來說更加清楚,但是卻更易於出錯。
    CSemaphore的構造:

CSemaphore( 
        LONG lInitialCount = 1,   
        LONG lMaxCount = 1,   
        LPCTSTR pstrName = NULL,   
        LPSECURITY_ATTRIBUTES lpsaAttributes = NULL   
        ); 

    1)lInitialCount:初始訪問計數值,必須不小於0,不大於最大計數值
    2)lMaxCount :允許訪問計數的最大值,必須大於0
    3)pstrName:“信號量”的名字,如果該對象用於進程間的處理,必須提供。如果爲NULL,該對象是未命名的。如果指定名字匹配一個已存在的信號,構造函數將引用該名稱的信號對象構造一個新的信號對象,如果指定的名字匹配一個已存在的非semaphore的同步對象,構造失敗。
    4)lpsaAttributes:對象的安全屬性。通常爲NULL。具體參見MSDN關於SECURITY_ATTRIBUTES

6. CMutex

    CMutex類的對象代表“啞程(mutex)”——它爲一個同步對象,只允許某一個線程獨自訪問同一資源。在僅僅一個線程被允許用於修改數據或其它被控制的資源時,啞程將變得非常有用。例如,給鏈接的列表增添一個結點就是隻允許一個線程的過程。通過使用CMutex對象來控制鏈接列表,此時只有一個線程能夠獲得列表的訪問權。
    若要使用CMutex 對象,首先要構造一個所需的CMutex 對象。然後指定希望等待的啞程的名稱,那麼應用最初就將擁有它。可以在構造函數返回時,訪問啞程。當你已經訪問了被控制的資源後,再調用CSyncObject::Unlock函數。 
    另外一種使用CMutex 對象的方法就是一個CMutex類型的變量,將其作爲你希望控制類的數據成員。在被控制對象的構造過程中,若啞程最初擁有了啞程的名稱或期待的安全屬性,那麼就調用CMutex數據成員指定的構造函數。以這種方式訪問由CMutex 對象控制的資源,首先要在資源訪問的成員函數中創建CSingleLock類型或CMultiLock類型的變量。然後調用封鎖對象的Lock成員函數(例如, CSingleLock::Lock)。這樣,你的線程要麼就獲得資源的訪問權,以等待將要釋放的資源,並獲取訪問權,要麼就等待將要釋放的資源,當超時後,返回失敗。在任何一種情況下,都可以在線程安全的模式下訪問資源。若要釋放這些資源,使用封鎖對象的Unlock成員函數(例如, CSingleLock::Unlock),或允許封鎖對象越界。
    CMutex的構造函數:

CMutex(
   BOOL bInitiallyOwn = FALSE,
   LPCTSTR lpszName = NULL,
   LPSECURITY_ATTRIBUTES lpsaAttribute = NULL 
);

    1)bInitiallyOwn:如果線程創建CMutex對象最初就通過mutex控制訪問資源,就指定該值
    2)lpszName :CMutex的名字。如果該對象用於進程間處理,必須指定該名字,如果爲NULL,則該對象是未命名的。如果已存在另一個同名的mutex,構造器將引用那個已存在的對象創建新的CMutex對象,如果已存在另一個同名但非mutex對象的同步類,構造將失敗。
    3)lpsaAttribute:安全屬性,通常爲NULL

7. CCriticalSection

    類CCriticalSection的對象表示一個“臨界區”,它是一個同步對象,同一時刻只允許一個線程訪問資源或代碼區。臨界區用於控制一次只有一個線程修改數據或其它的控制資源。例如,在鏈表中增加一個結點就只允許一次一個線程進行。通過使用CCriticalSection對象來控制鏈表,就可以達到這個目的。
    CCriticalSection對象的功能由當前的Win32對象--CRITICAL_SECTION提供。
    在運行性能比較重要而且資源不會跨進程使用時,建議採用臨界區代替Mutex。使用CCriticalSection對象之前,需要構造它。在構造函數返回後,就可以使用臨界區了。在使用完之後要調用UnLock函數。
    存取由CCriticalSection控制的資源時,要在資源的存取函數中定義一個CSingleLock型的變量。然後調用加鎖對象的Lock成員函數(如CSingleLock::Lock)。此時,調用的線程要麼獲得對資源的存取權,要麼等待他人釋放資源等待加鎖,或者等待他人釋放資源,但又因爲超時而加鎖失敗。這樣就保證了一次只有一個線程在存取臨界資源。釋放資源只需調用成員函數UnLock(例如CSingleLock:Unlock),或讓鎖對象在作用範圍之外。
 此外,可以單獨地建立一個CCriticalSection對象,並在存取臨界資源之前顯式地存取它。這種方式有助於保持代碼的清晰,但是更容易出錯,因爲程序員要記住在存取臨界資源前加鎖,存取之後開鎖。

8. CEvent

    CEvent類對象,表示一個“事件”——一個允許一個事件發生時線程通知另一個線程的同步對象。在一個線程需要了解何時執行任務時,事件是十分有用的。例如,拷貝數據到數據文檔時,線程應被通知何時數據是可用的。當新數據可用時,通過運用CEvent對象來通知拷貝線程,線程纔可能儘快地執行。 
    CEvent對象有兩種類型:自動和手工。一個手工CEvent對象存在於由ResetEvent 或SetEvent設置的狀態中,直到另一個函數被調用。一個自動CEvent對象在至少一個線程被釋放後自動返回一個無標記(無用的)狀態。
    要使用一個CEvent對象,應在需要時構造一個CEvent對象。指定要等待的事件的名字,最初擁有它的進程,就可以在構造函數返回時訪問事件。調用SetEvent標記(使可用)事件對象,然後當訪問完控制資源時,調用Unlock函數。
    另一個使用CEvent對象的方法是添加一個CEvent類型的變量,使之成爲希望控制的類的一個數據成員。在控制對象被構造期間,可調用CEvent數據成員的構造函數,它指明時間是否是最初就被標記、需要的事件對象類型、事件名稱(如果在進程中要使用)和所希望的安全屬性。 
    按以下方式訪問一個被CEvent對象控制的資源:首先創建在資源訪問成員函數中構造一個CSingleLock或CMultiLock類型的變量,然後調用封鎖對象的Lock成員函數(如CMultiLock::Lock)。此時,線程要麼可以訪問資源,等待資源釋放後訪問;要麼等待資源釋放而超時,訪問資源失敗。在各種情況下,資源都被以線程安全方式訪問。要釋放資源,可調用SetEvent來標識一個事件對象,然後使用封鎖對象的Unlock成員函數(如CMultiLock::UnLock),或允許封鎖對象超過範圍。

9. CSingleLock

    它代表用於多線程程序中資源訪問的控制機制。
    爲了使用同步對象,必須創建同步訪問類對象CSingleLock或CMultiLock 等待或釋放該同步對象。當只需要同一時間等待一個同步對象,使用CSingleLock;當某一特定時刻需要等待多個同步對象,使用CMultiLock。
    要使用CSingleLock,在資源訪問類得成員函數中構造該對象,然後調用該對象的成員函數IsLocked檢測資源是否可用,如果是,繼續執行資源訪問類的該成員函數的剩餘部分,如果資源不可用,等待特定是時間直到資源訪問控制被釋放或者訪問資源失敗。當資源訪問完成時,調用Unlock釋放資源控制權,或使構造的CSingleLock對象析構。

10. CMultiLock

    它代表用於多線程程序中資源訪問的控制機制。
    爲了使用同步對象,必須創建同步訪問類對象CSingleLock或CMultiLock 等待或釋放該同步對象。當只需要同一時間等待一個同步對象,使用CSingleLock;當某一特定時刻需要等待多個同步對象,使用CMultiLock。
    要使用CMultiLock,首先創建你想要等待的同步對象數組,接着,在再資源訪問類的成員函數中構造CMultiLock對象,然後,調用CMultiLock的Lock 檢測資源是否有效(有信號)。如果有資源有效,繼續資源訪問,如果沒有資源有效,等待資源訪問控制權釋放或訪問資源失敗。訪問資源完成後,調用Unlock 釋放資源控制權,或使CMultiLock超出作用域而析構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章