Windows下Critical Section、Event、Mutex、Semaphores區別

來源:互聯網
上載者:User

臨界區(Critical Section)

    保證在某一時刻只有一個線程能訪問資料的簡便辦法。在任意時刻只允許一個線程對共用資源進行訪問。如果有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶佔,並以此達到用原子方式操作共用資源的目的。

    臨界區包含兩個操作原語: EnterCriticalSection() 進入臨界區 LeaveCriticalSection() 離開臨界區

    EnterCriticalSection()語句執行後代碼將進入臨界區以後無論發生什麼,必須確保與之匹配的 LeaveCriticalSection()都能夠被執行到。否則臨界區保護的共用資源將永遠不會被釋放。在使用臨界區時,一般不允許其已耗用時間過長,只要進入臨界區的線程還沒有離開,其他所有試圖進入此臨界區的線程都會被掛起而進入到等待狀態,並會在一定程度上影響。程式的運行效能。尤其需要注意的是不要將等待使用者輸入或是其他一些外界幹預的操作包含到臨界區。如果進入了臨界區卻一直沒有釋放,同樣也會引起其他線程的長時間等待。換句話說,在執行了 EnterCriticalSection()語句進入臨界區後無論發生什麼,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。可以通過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程

    MFC提供了很多功能完備的類,我用MFC實現了臨界區。MFC為臨界區提供有一個CCriticalSection類,使用該類進行線程同步處理是非常簡單的。只需線上程函數中用CCriticalSection類成員函數Lock()和UnLock()標定出被保護程式碼片段即可。Lock()後代碼用到的資源自動被視為臨界區內的資源被保護。UnLock後別的線程才能訪問這些資源。

互斥量(Mutex)

   互斥(Mutex)是一種用途非常廣泛的核心對象。能夠保證多個線程對同一共用資源的互斥訪問。同臨界區有些類似,只有擁有互斥對象的線程才具有訪問資源的許可權,由於互斥對象只有一個,因此就決定了任何情況下此共用資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。與其他幾種核心對象不同,互斥對象在作業系統中擁有特殊代碼,並由作業系統來管理,作業系統甚至還允許其進行一些其他核心對象所不能進行的非常規操作。 互斥量跟臨界區很相似,只有擁有互斥對象的線程才具有訪問資源的許可權,由於互斥對象只有一個,因此就決定了任何情況下此共用資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。互斥量比臨界區複雜。因為使用互斥不僅僅能夠在同一應用程式不同線程中實現資源的安全共用,而且可以在不同應用程式的線程之間實現對資源的安全共用。

    以互斥核心對象來保持線程同步可能用到的函數主要有CreateMutex()、OpenMutex()、ReleaseMutex()、 WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過 CreateMutex()或OpenMutex()建立或開啟一個互斥對象。CreateMutex()函數原型為:

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性指標
 BOOL bInitialOwner, // 初始擁有者
 LPCTSTR lpName // 互斥對象名
);

  參數bInitialOwner主要用來控制互斥對象的初始狀態。一般多將其設定為FALSE,以表明互斥對象在建立時並沒有為任何線程所佔有。如果在建立互斥對象時指定了對象名,那麼可以在本進程其他地方或是在其他進程通過OpenMutex()函數得到此互斥對象的控制代碼。 OpenMutex()函數原型為:

HANDLE OpenMutex(
 DWORD dwDesiredAccess, // 訪問標誌
 BOOL bInheritHandle, // 繼承標誌
 LPCTSTR lpName // 互斥對象名
);

  當目前對資源具有訪問權的線程不再需要訪問此資源而要離開時,必須通過ReleaseMutex()函數來釋放其擁有的互斥對象,其函數原型為:

BOOL ReleaseMutex(HANDLE hMutex);

  其唯一的參數hMutex為待釋放的互斥物件控點。至於WaitForSingleObject()和 WaitForMultipleObjects()等待函數在互斥對象保持線程同步中所起的作用與在其他核心對象中的作用是基本一致的,也是等待互斥核心對象的通知。但是這裡需要特別指出的是:在互斥對象通知引起調用等待函數返回時,等待函數的傳回值不再是通常的WAIT_OBJECT_0(對於 WaitForSingleObject()函數)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之間的一個值(對於 WaitForMultipleObjects()函數),而是將返回一個WAIT_ABANDONED_0(對於 WaitForSingleObject()函數)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之間的一個值(對於WaitForMultipleObjects()函數)。以此來表明線程正在等待的互斥對象由另外一個線程所擁有,而此線程卻在使用完共用資源前就已經終止。除此之外,使用互斥對象的方法在等待線程的可調度性上同使用其他幾種核心對象的方法也有所不同,其他核心對象在沒有得到通知時,受調用等待函數的作用,線程將會掛起,同時失去可調度性,而使用互斥的方法卻可以在等待的同時仍具有可調度性,這也正是互斥對象所能完成的非常規操作之一。

  在編寫程式時,互斥對象多用在對那些為多個線程所訪問的記憶體塊的保護上,可以確保任何線程在處理此記憶體塊時都對其擁有可靠的獨佔訪問權。

    互斥對象在MFC中通過CMutex類進行表述。使用CMutex類的方法非常簡單,在構造CMutex類對象的同時可以指明待查詢的互斥對象的名字,在建構函式返回後即可訪問此互斥變數。CMutex類也是只含有建構函式這唯一的成員函數,當完成對互斥對象保護資源的訪問後,可通過調用從父類 CSyncObject繼承的UnLock()函數完成對互斥對象的釋放。CMutex類建構函式原型為:

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

  該類的適用範圍和實現原理與API方式建立的互斥核心對象是完全類似的,但要簡潔的多。

訊號量(Semaphores)

    訊號量對象對線程的同步方式與前面幾種方法不同,訊號允許多個線程同時使用共用資源,這與作業系統中的PV操作相同。它指出了同時訪問共用資源的線程最大數目。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()建立訊號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設定為最大資源計數,每增加一個線程對共用資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出訊號量訊號。但是當前可用計數減小到0時則說明當前佔用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的訊號量訊號將無法發出。線程在處理完共用資源後,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大於最大資源計數。 訊號量是通過計數來對線程訪問資源進行控制的,而實際上訊號量確實也被稱作Dijkstra計數器。

    PV操作及訊號量的概念都是由荷蘭科學家E.W.Dijkstra提出的。訊號量S是一個整數,S大於等於零時代表可供並發進程使用的資源實體數,但S小於零時則表示正在等待使用共用資源的進程數。

    P操作申請資源:
    (1)S減1;
    (2)若S減1後仍大於等於零,則進程繼續執行;
    (3)若S減1後小於零,則該進程被阻塞後進入與該訊號相對應的隊列中,然後轉入進程調度。
  
    V操作 釋放資源:
    (1)S加1;
    (2)若相加結果大於零,則進程繼續執行;
    (3)若相加結果小於等於零,則從該訊號的等待隊列中喚醒一個等待進程,然後再返回原進程繼續執行或轉入進程調度。

    使用訊號量核心對象進行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、 ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數。其中,CreateSemaphore()用來建立一個訊號量核心對象,其函數原型為:

HANDLE CreateSemaphore(
 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指標
 LONG lInitialCount, // 初始計數
  LONG lMaximumCount, // 最大計數
 LPCTSTR lpName // 對象名指標
);

  參數lMaximumCount是一個有符號32位值,定義了允許的最大資源計數,最大取值不能超過4294967295。lpName參數可以為建立的訊號量定義一個名字,由於其建立的是一個核心對象,因此在其他進程中可以通過該名字而得到此訊號量。OpenSemaphore()函數即可用來根據訊號量名開啟在其他進程中建立的訊號量,函數原型如下:

HANDLE OpenSemaphore(
 DWORD dwDesiredAccess, // 訪問標誌
 BOOL bInheritHandle, // 繼承標誌
 LPCTSTR lpName // 訊號量名
);

  線上程離開對共用資源的處理時,必須通過ReleaseSemaphore()來增加當前可用資源計數。否則將會出現當前正在處理共用資源的實際線程數並沒有達到要限制的數值,而其他線程卻因為當前可用資源計數為0而仍無法進入的情況。ReleaseSemaphore()的函數原型為:

BOOL ReleaseSemaphore(
 HANDLE hSemaphore, // 訊號量控制代碼
 LONG lReleaseCount, // 計數遞增數量
 LPLONG lpPreviousCount // 先前計數
);

  該函數將lReleaseCount中的值添加給訊號量的當前資源計數,一般將lReleaseCount設定為1,如果需要也可以設定其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進入共用資源的線程函數入口處,主要用來判斷訊號量的當前可用資源計數是否允許本線程的進入。只有在當前可用資源計數值大於0時,被監視的訊號量核心對象才會得到通知。

  訊號量的使用特點使其更適用於對Socket(通訊端)程式中線程的同步。例如,網路上的HTTP伺服器要對同一時間內訪問同一頁面的使用者數加以限制,這時可以為沒一個使用者對伺服器的頁面請求設定一個線程,而頁面則是待保護的共用資源,通過使用訊號量對線程的同步作用可以確保在任一時刻無論有多少使用者對某一頁面進行訪問,只有不大於設定的最大使用者數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有使用者退出對此頁面的訪問後才有可能進入。

在MFC中,通過CSemaphore類對訊號量作了表述。該類只具有一個建構函式,可以構造一個訊號量對象,並對初始資源計數、最大資源計數、對象名和安全屬性等進行初始化,其原型如下:

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

  在構造了CSemaphore類對象後,任何一個訪問受保護共用資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過 CSemaphore類也可以將前面的線程同步代碼進行改寫,這兩種使用訊號量的線程同步方法無論是在實現原理上還是從實現結果上都是完全一致的。

事件(Event)

    事件對象也可以通過通知操作的方式來保持線程的同步。並且可以實現不同進程中的線程同步操作。

    訊號量包含的幾個操作原語:
    CreateEvent() 建立一個訊號量
    OpenEvent() 開啟一個事件
    SetEvent() 回置事件
    WaitForSingleObject() 等待一個事件
    WaitForMultipleObjects() 等待多個事件

    使用臨界區只能同步同一進程中的線程,而使用事件核心對象則可以對進程外的線程進行同步,其前提是得到對此事件對象的訪問權。可以通過 OpenEvent()函數擷取得到,其函數原型為:

HANDLE OpenEvent(
 DWORD dwDesiredAccess, // 訪問標誌
 BOOL bInheritHandle, // 繼承標誌
 LPCTSTR lpName // 指向事件對象名的指標
);

  如果事件對象已建立(在建立事件時需要指定事件名),函數將返回指定事件的控制代碼。對於那些在建立事件時沒有指定事件名的事件核心對象,可以通過使用核心對象的繼承性或是調用DuplicateHandle()函數來調用CreateEvent()以獲得對指定事件對象的訪問權。在擷取到訪問權後所進行的同步操作與在同一個進程中所進行的線程同步操作是一樣的。

  如果需要在一個線程中等待多個事件,則用WaitForMultipleObjects()來等待。 WaitForMultipleObjects()與WaitForSingleObject()類似,同時監視位於控制代碼數組中的所有控制代碼。這些被監視對象的控制代碼享有平等的優先權,任何一個控制代碼都不可能比其他控制代碼具有更高的優先權。WaitForMultipleObjects()的函數原型為:

DWORD WaitForMultipleObjects(
 DWORD nCount, // 等待控制代碼數
 CONST HANDLE *lpHandles, // 控制代碼數組首地址
 BOOL fWaitAll, // 等待標誌
 DWORD dwMilliseconds // 等待時間間隔
);

  參數nCount指定了要等待的核心對象的數目,存放這些核心對象的數組由lpHandles來指向。fWaitAll對指定的這nCount 個核心對象的兩種等待方式進行了指定,為TRUE時當所有對象都被通知時函數才會返回,為FALSE則只要其中任何一個得到通知就可以返回。 dwMilliseconds在這裡的作用與在WaitForSingleObject()中的作用是完全一致的。如果等待逾時,函數將返回 WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個值,則說明所有指定對象的狀態均為已通知狀態(當fWaitAll為TRUE時)或是用以減去WAIT_OBJECT_0而得到發生通知的對象的索引(當fWaitAll為FALSE 時)。如果傳回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示所有指定對象的狀態均為已通知,且其中至少有一個對象是被丟棄的互斥對象(當fWaitAll為TRUE時),或是用以減去WAIT_OBJECT_0表示一個等待正常結束的互斥對象的索引(當fWaitAll為FALSE時)。

MFC為事件相關處理也提供了一個CEvent類,共包含有除建構函式外的4個成員函數 PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別相當與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數。而建構函式則履行了原 CreateEvent()函數建立事件對象的職責,其函數原型為:

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

  按照此預設設定將建立一個自動複位、初始狀態為複位狀態的沒有名字的事件對象。封裝後的CEvent類使用起來更加方便,

事件可以實現不同進程中的線程同步操作,並且可以方便的實現多個線程的優先比較等待操作,例如寫多個WaitForSingleObject來代替 WaitForMultipleObjects從而使編程更加靈活。

總結:

    1. 互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以建立互斥量需要的資源更多,所以如果只為了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。因為互斥量是跨進程的互斥量一旦被建立,就可以通過名字開啟它。

    2. 互斥量(Mutex),號誌(Semaphore),事件(Event)都可以被跨越進程使用來進行同步資料操作,而其他的對象與資料同步操作無關,但對於進程和線程來講,如果進程和線程在運行狀態則為無訊號狀態,在退出後為有訊號狀態。所以可以使用WaitForSingleObject來等待進程和線程退出。

    3. 通過互斥量可以指定資源被獨佔的方式使用,但如果有下面一種情況通過互斥量就無法處理,比如現在一位使用者購買了一份三個並發訪問許可的資料庫系統,可以根據使用者購買的訪問許可數量來決定有多少個線程/進程能同時進行資料庫操作,這時候如果利用互斥量就沒有辦法完成這個要求,號誌對象可以說是一種資源計數器。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.