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


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