2.[個人]C++線程入門到進階(2)----線程同步之信號量(semaphore)

1、首先來看看如何使用信號量

    信號量Semaphore常用有三個函數,使用很方便。下面是這幾個函數的原型和使用說明。

第一個 CreateSemaphore

函數功能:創建信號量

函數原型:

HANDLE CreateSemaphore(

  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

  LONG lInitialCount,

  LONG lMaximumCount,

  LPCTSTR lpName

);

函數說明:

第一個參數表示安全控制,一般直接傳入NULL

第二個參數表示初始資源數量。

第三個參數表示最大併發數量。

第四個參數表示信號量的名稱,傳入NULL表示匿名信號量。

第二個 OpenSemaphore

函數功能:打開信號量

函數原型:

HANDLE OpenSemaphore(

  DWORD dwDesiredAccess,

  BOOL bInheritHandle,

  LPCTSTR lpName

);

函數說明:

第一個參數表示訪問權限,對一般傳入SEMAPHORE_ALL_ACCESS。詳細解釋可以查看MSDN文檔。

第二個參數表示信號量句柄繼承性,一般傳入TRUE即可。

第三個參數表示名稱,不同進程中的各線程可以通過名稱來確保它們訪問同一個信號量。

第三個 ReleaseSemaphore

函數功能:遞增信號量的當前資源計數

函數原型:

BOOL ReleaseSemaphore(

  HANDLE hSemaphore,

  LONG lReleaseCount

  LPLONG lpPreviousCount

);

函數說明:

第一個參數是信號量的句柄。

第二個參數表示增加個數,必須大於0且不超過最大資源數量。

第三個參數可以用來傳出先前的資源計數,設爲NULL表示不需要傳出。

       注意:當前資源數量大於0,表示信號量處於觸發,等於0表示資源已經耗盡故信號量處於末觸發。在對信號量調用等待函數時,等待函數會檢查信號量的當前資源計數,如果大於0(即信號量處於觸發狀態),減1後返回讓調用線程繼續執行。一個線程可以多次調用等待函數來減小信號量。

最後一個 信號量的清理與銷燬

由於信號量是內核對象,因此使用CloseHandle()就可以完成清理與銷燬了。

2、在經典多線程問題中設置一個信號量和一個關鍵段。用信號量處理主線程與子線程的同步,用關鍵段來處理各子線程間的互斥詳見代碼:

[html] view plain copy
  1. #include <stdio.h>  
  2. #include <process.h>  
  3. #include <windows.h>  
  4. long g_nNum;  
  5. unsigned int __stdcall Fun(void *pPM);  
  6. const int THREAD_NUM = 10;  
  7. //信號量與關鍵段  
  8. HANDLE            g_hThreadParameter;  
  9. CRITICAL_SECTION  g_csThreadCode;  
  10. int main()  
  11. {  
  12.     printf("     經典線程同步 信號量Semaphore\n");  
  13.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  14.   
  15.     //初始化信號量和關鍵段  
  16.     g_hThreadParameter = CreateSemaphore(NULL, 0, 1, NULL);//當前0個資源,最大允許1個同時訪問  
  17.     InitializeCriticalSection(&g_csThreadCode);  
  18.   
  19.     HANDLE  handle[THREAD_NUM];   
  20.     g_nNum = 0;  
  21.     int i = 0;  
  22.     while (i < THREAD_NUM)   
  23.     {  
  24.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);  
  25.         WaitForSingleObject(g_hThreadParameter, INFINITE);//等待信號量>0  
  26.         ++i;  
  27.     }  
  28.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  29.       
  30.     //銷燬信號量和關鍵段  
  31.     DeleteCriticalSection(&g_csThreadCode);  
  32.     CloseHandle(g_hThreadParameter);  
  33.     for (i = 0; i < THREAD_NUM; i++)  
  34.         CloseHandle(handle[i]);  
  35.     return 0;  
  36. }  
  37. unsigned int __stdcall Fun(void *pPM)  
  38. {  
  39.     int nThreadNum = *(int *)pPM;  
  40.     ReleaseSemaphore(g_hThreadParameter, 1, NULL);//信號量++  
  41.   
  42.     Sleep(50);//some work should to do  
  43.   
  44.     EnterCriticalSection(&g_csThreadCode);  
  45.     ++g_nNum;  
  46.     Sleep(0);//some work should to do  
  47.     printf("線程編號爲%d  全局資源值爲%d\n", nThreadNum, g_nNum);  
  48.     LeaveCriticalSection(&g_csThreadCode);  
  49.     return 0;  
  50. }  

運行結果:

可以看出來,信號量也可以解決線程之間的同步問題。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

補充:

在開發軟件的過程中,多線程的程序往往需要實現相互通訊,比如幾個線程添加一個消息到隊列裏,而另一個線程在睡眠時,就需要喚醒那個線程來處理事情。在這其中,就需要使用到信號量來進行同步。CreateSemaphore是創建信號量,ReleaseSemaphore是增加信號量。

函數CreateSemaphore和ReleaseSemaphore聲明如下:
WINBASEAPI
__out
HANDLE
WINAPI
CreateSemaphoreA(
    __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    __in     LONG lInitialCount,
    __in     LONG lMaximumCount,
    __in_opt LPCSTR lpName
    );
WINBASEAPI
__out
HANDLE
WINAPI
CreateSemaphoreW(
    __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    __in     LONG lInitialCount,
    __in     LONG lMaximumCount,
    __in_opt LPCWSTR lpName
    );
#ifdef UNICODE
#define CreateSemaphore CreateSemaphoreW
#else
#define CreateSemaphore CreateSemaphoreA
#endif // !UNICODE

lpSemaphoreAttributes是信號量的安全屬性。
lInitialCount是初始化的信號量。
lMaximumCount是允許信號量增加到最大值。
lpName是信號量的名稱。

WINAPI
ReleaseSemaphore(
    __in      HANDLE hSemaphore,
    __in      LONG lReleaseCount,
    __out_opt LPLONG lpPreviousCount
    );

hSemaphore是要增加的信號量句柄。
lReleaseCount是增加的計數。
lpPreviousCount是增加前的數值返回。

調用函數的例子如下:
#001 //線程運行函數。
#002 //在這裏可以使用類裏的成員,也可以讓派生類實現更強大的功能。
#003 //蔡軍生 2007/10/10 QQ:9073204 深圳
#004 DWORD CThreadSemaphore::Run(void)
#005 {
#006 //輸出到調試窗口。
#007 ::OutputDebugString(_T("Run()線程函數運行\r\n"));      
#008 
#009 //
#010 const LONG cMax = 10;
#011   m_hSemaphore = CreateSemaphore( 
#012        NULL,   // 缺省的安全屬性。
#013        0,   // 初始化爲0個信號量。
#014        cMax,   // 最大爲10個信號量。
#015        NULL); // 不命名。
#016 
#017 if (m_hSemaphore == NULL) 
#018 {
#019         return -1;
#020 }
#021 
#022 //
#023 const int nMaxObjs = 2;
#024 HANDLE hWaitObjects[nMaxObjs] = {m_hEventExit,m_hSemaphore};
#025 
#026 //線程循環。
#027 for (;;)
#028 {
#029         DWORD dwRet = WaitForMultipleObjects(nMaxObjs,hWaitObjects,FALSE,INFINITE);
#030         if (dwRet == WAIT_TIMEOUT)
#031         {
#032               //可以繼續運行。                 
#033               TCHAR chTemp[128];
#034               wsprintf(chTemp,_T("CThreadSemaphore::Run() ThreadID=%d\r\n"),m_dwThreadID);
#035               ::OutputDebugString(chTemp);
#036 
#037               //目前沒有做什麼事情,就讓線程釋放一下CPU。
#038               Sleep(10);
#039         }
#040         else if (dwRet == WAIT_OBJECT_0)
#041         {
#042               //退出線程。
#043               ::OutputDebugString(_T("Run() 退出線程\r\n"));
#044               break;
#045         }
#046         else if (dwRet == WAIT_OBJECT_0+1)
#047         {
#048               //可以繼續運行。                 
#049               TCHAR chTemp[128];
#050               wsprintf(chTemp,_T("CThreadSemaphore::Run() Semaphore,ThreadID=%d\r\n"),m_dwThreadID);
#051               ::OutputDebugString(chTemp);
#052 
#053               //
#054 
#055         }
#056         else if (dwRet == WAIT_ABANDONED)
#057         {
#058               //出錯。
#059               ::OutputDebugString(_T("Run() 線程出錯\r\n"));
#060               return -1;
#061         }
#062 }
#063 
#064 //
#065 if (m_hSemaphore)
#066 {
#067         CloseHandle(m_hSemaphore);
#068          m_hSemaphore = NULL;
#069 }
#070 
#071 return 0;
#072 }
#073 
第11行就是創建信號量。
第29行等信號量事件和退出事件。


#001 
#002 //
#003 //增加信號量
#004 //蔡軍生 2007/10/10 QQ:9073204 深圳
#005 //
#006 void IncSemaphore(void)
#007 {
#008         if (m_hSemaphore)
#009         {
#010              if (!ReleaseSemaphore( 
#011                    m_hSemaphore, // 要增加的信號量。
#012                   1,           // 增加1.
#013                   NULL) )      // 不想返回前一次信號量。
#014               {
#015 
#016               }
#017         }
#018 }
#019

Semaphore是另一個同步問題機制,不論是Event或Mutex,其他Process在執WaitForSingleObject
時,就看當時的物件是Signal或UnSignal而決定是否等待,而Semaphore也相同,但是它
要變成Signal /UnSignal的狀態,卻有些不同,它是提供一個計數值,它允許在這個計數
值之內,任何執行到WaitForSingleObject的Thread都不會停下來,而且每執行
WaitForSingleObject一次,計數值就減一,當計數值變成0時,該Semaphore纔會處於
UnSignal的狀態,而某個Thread ReleaseSemaphore時,便會將計數值增加,以便其他的
Thread或本身可得Signal的訊號,而使WaitForSingleObject停止等待。

例如說,該電腦只有兩個 COM PORT,所以只允許兩個計數值同時使用COM PORT,因此,

    hSema = CreateSemaphore(ByVal 0&, 2, 2, "MySema")

第2個叄數表示:剛開始的時候,有多少個COM PORT可使用
第3個叄數表示:最多有多少個COM PORT可使用
第4個叄數:Semaphore的名稱,只要名稱相同,則傳回的handle(hSema)會指向相同的
Semaphore物件。因此,要使用相同的名稱來Create Semaphore才能達共用
一個Semaphore的效果。
而使用WaitForSingleObject來Check看看是否還有剩下的COM Port可使用,如果還有剩
(計數值 > 0),則沒有等待而可執行下一行指令,同時,計數值減1。若有第三個要求
COM PORT的使用,那它就得等待,直到有Thread執行

    ReleaseSemaphore(hSema, 1, count)

第2個叄數表示:Release多少個COM PORT出來,一般來說都是1,表示一個ReleaseSemaphore
       會將計數器的值加一,但是您也可以指定 > 1的值,代表一口氣增加計數器
       的值( + n , n > 1)。例如,您的程式一口氣使用了兩個COM PORT,並假設
       您於程式中有使用WaitForSingleObject兩次,程式最後,使用
       ReleaseSemaphore(hSema, 2, count)而不必
       ReleaseSemaphore(hSema, 1, count)執行兩次。
第3個叄數表示:ReleaseSemaphore執行之前計數器原來的值。

Semaphore和Event有個地方相同,那就是沒有Owner的觀念,即Thread A 所Create出的
Semaphore物件,於Thread B中執行ReleaseSemaphore時,依然會增加計數器的值

'以下程式需兩個Command BUTTON 一個ListBox,併產生兩個執行個體來做Private Const INFINITE = &HFFFFPrivate Declare Function CreateSemaphore Lib "kernel32" Alias "CreateSemaphoreA" (lpSemaphoreAttributes As Any, ByVal lInitialCount As Long, ByVal lMaximumCount As Long, ByVal lpName As String) As Long ' modified by KJPrivate Declare Function OpenSemaphore Lib "kernel32" Alias "OpenSemaphoreA" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal lpName As String) As LongPrivate Declare Function ReleaseSemaphore Lib "kernel32" (ByVal hSemaphore As Long, ByVal lReleaseCount As Long, lpPreviousCount As Long) As LongPrivate Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As LongPrivate Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As LongPrivate hSema As LongPrivate Sub Command1_Click() Call WaitForSingleObject(hSema, INFINITE) '一直等到有可用的COM PORT才結束 List1.AddItem "可用COM PORT 減一" List1.ListIndex = List1.NewIndex '在此處理Com Port的程式End SubPrivate Sub Command2_Click() Dim count As Long '在此處理com port Close的動作 If ReleaseSemaphore(hSema, 1, count) Then List1.AddItem "剩下可用COM PORT等於:" & count + 1 Else List1.AddItem "Semaphore Release 無效" End If List1.ListIndex = List1.NewIndexEnd SubPrivate Sub Form_Load() hSema = CreateSemaphore(ByVal 0&, 2, 2, "MySema")End SubPrivate Sub Form_Unload(Cancel As Integer) Call CloseHandle(hSema)End Sub


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