[windows][thread] 同步.核心.Mutex.Semaphores等

來源:互聯網
上載者:User

概述 :   

        非核心對象臨界區非常適合於序列化對一個進程中的資料的訪問,因為它們的速度很快。但我們或許想要使一些應用程式與電腦中發生的其它特殊事件或者其它進程中執行的操作取得同步。這時臨界區無能為力。就需要使用核心對象來同步。

 

       可以使用下列核心對象可用來同步線程:

        1.         進程,Processes

        2.         線程,Threads

        3.         檔案,Files

        4.         控制台輸入,Console input

        5.         檔案變化通知,File change notifications

        6.         互斥量,Mutexes

        7.         訊號量,Semaphores

        8.         事件(自動重設事件和手動重設事件),Events

        9.         可等的計時器(只用於Window NT4或更高),Waitable timers

        10.     Jobs

 

        每一個上面這些類型的對象都可以處於兩種狀態之一:有訊號(signaled)和無訊號(nonsignaled)。可用就是有訊號狀態,被佔用就是無訊號狀態。比如進程和線程在終結時其核心對象變為有訊號,而在它們處於建立和正在運行時,其核心對象是無訊號的

 

核心對象同步應用:

1.         某線程獲得某進程的核心物件控點時,可以改變進程優先順序、獲得進程的退出碼;使本線程與某進程的終結取得同步等等。

2.         當獲得某線程的核心物件控點時,可以:改變該線程運行狀態、與該線程的終結取得同步等等。

3.         當獲得檔案控制代碼時,可以:本線程可與某一個非同步檔案的I/O操作獲得同步等等。

4.         控制台輸入對象可用來使線程在有輸入進入時被喚醒以執行相關任務等等。

5.         其它核心對象―――檔案改變通知、互斥量、訊號量、事件、可等計時器等―――都只是為了同步對象而存在。相應的,也有WIN32函數來建立、開啟、關閉這些對象,將線程與這些對象同步。

  互斥量(Mutex)的專屬特性:        Mutex,也就是互斥量,同步基元的意思。當兩個或更多線程需要同時訪問一個共用資源時,系統需要使用同步機制來確保一次只有一個線程使用該資源。 Mutex 只向一個線程授予對共用資源的獨佔訪問權。 如果一個線程擷取了互斥體,則要擷取該互斥體的第二個線程將被掛起,直到第一個線程釋放該互斥體。              互斥量對象與所有其它核心對象的不同之處在於它是被線程所擁有的。其它所有同步對象要麼有訊號,要麼無訊號,僅此而已。而互斥量對象除了記錄當前訊號狀態外,還要記住此時那個線程擁有它。如果一個線程在得到一個互斥量對象 (即將其置為無訊號態)後就終結了,互斥量也就廢棄了。在這種情況了,互斥量將永遠保持無訊號態,因為沒有其它線程能夠通過調用ReleaseMutex來釋放它。

        系統發現產生這種情況時,就自動將互斥量設回有訊號狀態。其它等待該訊號量的線程就會被喚醒,但函數的傳回值為WAIT_ABANDONED而不是正常的WAIT_OBJECT_0。這時,其它線程可以知道互斥量是不是被正常釋放。

        其它的,互斥量與CRITICAL_SECTION類似。擁有該互斥量的線程,每次調用WaitForSingleObject都會立即成功返回,但互斥量的使用計數將增加,同樣的,也要多次調用ReleaseMutex以使引用計數變為零,方可供別的線程使用。

  幾點疑問:

問:其它核心對象線上程異常終止沒有釋放所有權時,系統回重設其狀態嗎?如果重設,將沒有任何標記,與正常釋放無異,即不會擁有互斥量的這個返回WAIT_ABANDONED的特性?

 

【注意:線程擁有某個核心對象線程擁有某個核心對象的所有權這二者是不同的。當說線程擁有某個核心對象時,要強調的是當該線程終止時,若線程正好擁有該核心對象的訪問權,核心對象也將被廢棄,因為不能重設其訊號狀態;而線程擁有某一個核心對象的所用權,指的是線程可以調用某些函數,訪問該核心對象或對該核心對象執行某些操作】

 

1:互斥量(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()函數)。以此來表明線程正在等待的互斥對象由另外一個線程所擁有,而此線程卻在使用完共用資源前就已經終止。除此之外,使用互斥對象的方法在等待線程的可調度性上同使用其他幾種核心對象的方法也有所不同,其他核心對象在沒有得到通知時,受調用等待函數的作用,線程將會掛起,同時失去可調度性,而使用互斥的方法卻可以在等待的同時仍具有可調度性,這也正是互斥對象所能完成的非常規操作之一。        在編寫程式時,互斥對象多用在對那些為多個線程所訪問的記憶體塊的保護上,可以確保任何線程在處理此記憶體塊時都對其擁有可靠的獨佔訪問權。 

2:訊號量(Semaphores)

      訊號量對象對線程的同步方式與前面幾種方法不同,訊號允許多個線程同時使用共用資源,這與作業系統中的PV操作相同。它指出了同時訪問共用資源的線程最大數目。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()建立訊號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設定為最大資源計數,每增加一個線程對共用資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出訊號量訊號。但是當前可用計數減小到0時則說明當前佔用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的訊號量訊號將無法發出。線程在處理完共用資源後,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大於最大資源計數。 訊號量是通過計數來對線程訪問資源進行控制的,而實際上訊號量確實也被稱作Dijkstra計數器。        訊號量核心對象進行線程同步主要會用到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伺服器要對同一時間內訪問同一頁面的使用者數加以限制,這時可以為沒一個使用者對伺服器的頁面請求設定一個線程,而頁面則是待保護的共用資源,通過使用訊號量對線程的同步作用可以確保在任一時刻無論有多少使用者對某一頁面進行訪問,只有不大於設定的最大使用者數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有使用者退出對此頁面的訪問後才有可能進入。 

3: 事件(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時)。  總結:: 1:線程等待操作

     線程主要使用兩個函數將本身設為睡眠來等待核心對象變為有訊號:即這兩個函數都是阻塞函數。

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD dwMilliseconds
);
DWORD WaitForMultipleObjects(
  DWORD nCount,
  const HANDLE* lpHandles,  // HANDLE數組
  BOOL bWaitAll,
  DWORD dwMilliseconds
);

        WaitForSingleObject,在一個指定時間(dwMilliseconds)內等待某一個核心對象變為有訊號,在此時間內,若等待的核心對象一直是無訊號的,則調用線程將睡眠,否則繼續執行。超過此時間後,線程繼續運行。函數傳回值可能為:WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED(僅當核心對象為互斥量時)、WAIT_FAILED。

        WaitForMultipleObjects與WaitForSingleObject類似,只是它要麼等待指定列表(由lpHandles指定)中若干個對象(由nCount決定)都變為有訊號,要麼等待一個列表(由lpHandles指定)中的某一個對象變為有訊號(由bWaitAll決定)。

       WaitForSingleObject和WaitForMultipleObjects函數對特定的核心對象有重要的副作用,即它們根據不同的核心對象,會決定是否改變核心對象的訊號狀態,並執行這種改變;這些副作用,決定了是讓等待該核心對象的進程或線程中的某一個被喚醒還是全都被喚醒。

(1)  對進程和線程核心對象,這兩個函數不產生副作用。

      即,在進程或線程核心對象變為有訊號後,它們將保持有訊號,這兩個函數不會試圖改變核心對象的訊號狀態。這樣,所有等待這些核心對象的線程都會被喚醒。

(2)     對於互斥量、自動重設事件和自動重設可等的計時器對象,這兩個函數將把它們的狀態改為無訊號。

     換言之一旦這些對象變為有訊號並且有一個線程被喚醒,則對象重被置為無訊號狀態。於是,只有一個正在等待的線程醒來,其它等待的線程將繼續睡眠。

(3)    對於 WaitForMultipleObjects 函數還有非常重要的一個特性:當調用它時傳遞的bWaitAll為TRUE時,在所有被等待的對象都變為有訊號之前,被等待的任何可以被改變狀態的核心對象都不被重設為無訊號狀態。換言之, 在傳入參數 bWaitAll 為TRUE ,WaitForMultipleObjects 除非能取得所有指定對象(由lpHandles 指定)的所有權,它不會取得單個對象的所有權(不能取得所有權,自然也不會改變此對象的訊號狀態)。這是為了防止死結。換言之,在bWaitAll為TRUE時,WaitForMultipleObjects不會在沒有獲得所有被等對象所有權的情形下改變某一可以被改變狀態的核心對象的訊號狀態,任何以同樣方式等待的線程都不會被喚醒,但以其它方式等待的線程將被喚醒。 2:比較分析

     (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.