CPP-基础:信号量
windows api 多线程---信号量
信号量(Semaphore)和互斥量一样,属于内核对象。它自动执行可用资源查询的测试,如果有可用资源,则可用资源的计数减少1,从而避免其它线程请求资源。当该线程释放该资源后,可用资源计数增加1,则操作系统允许另一个线程请求资源。
信号量与临界区和互斥量的不同在于,它不属于某个线程。也就是说,一个线程可以等待信号量对象(减少它的资源计数),而另一个线程释放该对象(增加它的资源计数)。
图3 使用信号量对象控制资源
下面结合图例3来演示信号量对象对资源的控制。在图3中,以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图(a)所示,最大资源计数和当前可用资源计数均为4,此后每增加一个对资源进行访问的线程(用黑色箭头表示)当前资源计数就会相应减1,图(b)即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时,将如图(c)所示,此时已达到最大资源计数,而当前可用资源计数也已减到0,其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后,将会释放出空间,图(d)已有两个线程退出对资源的占有,当前可用计数为2,可以再允许2个线程进入到对资源的处理。可以看出,信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。
Win32 API提供了几个函数用于支持信号量。使用Win32 API产生一个信号量,必须首先调用CreateSemaphore()函数,该函数描述如下:
创建一个信号量
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);
*@param lpSemaphoreAttributes 指定安全属性,如果是NULL就表示使用默认属性。
*@param lInitialCount 用于指定该信号量的初始资源计数,必须大于或等于0,并且小于或等于lMaximumCount。
*@param lMaximumCount 指定信号量的最大资源计数。
*@param lpName 是赋给信号量的字符串名字。
你可以根据信号量的字符串名字得到该信号量的句柄:
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
*@param dwDesiredAccess 访问方式,同互斥量参数。
*@param bInheritHandle 继承特性,同互斥量参数。
*@param lpName 信号量名字。
释放信号量函数:
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
*@param hSemaphore 信号量的句柄。
*@param lReleaseCount指信号量现值的增额,该值必须大于0。
*@param lpPreviousCount 传回信号量原来的计数,可以为NULL。
释放信号量函数与释放互斥量函数形式相同,但不同之处在于:
一、任意线程可以在任意时刻调用此函数,因为信号量对象不为某个线程拥有。
二、ReleaseSemaphore()函数可以用大于1的大小来增加信号量的资源计数。
例1:
1 #include "stdafx.h" 2 #include <stdio.h> 3 #include <windows.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #define threadnum 10 7 typedef struct THREADDATA 8 { 9 int id; 10 char name[10]; 11 int sleep; 12 }THREADDATA; 13 14 HANDLE handleSemaphore; 15 char * str; 16 DWORD WINAPI ThreadProc( LPVOID lpParam ) 17 { 18 THREADDATA *data=(THREADDATA *)lpParam; 19 20 WaitForSingleObject(handleSemaphore,INFINITE); 21 for(int i=0;i<10;i++) 22 { 23 // WaitForSingleObject(handleSemaphore,INFINITE); 24 printf("thread%d:%d\n",data->id,i); 25 // ReleaseSemaphore(handleSemaphore,1,NULL); 26 Sleep(data->sleep); 27 } 28 ReleaseSemaphore(handleSemaphore,1,NULL); 29 return 0; 30 } 31 int main(int argc, char* argv[]) 32 { 33 str=(char*)malloc(30); 34 THREADDATA pData[threadnum]; 35 DWORD dwThreadId[threadnum]; 36 HANDLE hThread[threadnum]; 37 handleSemaphore=CreateSemaphore(NULL,1,2,"thread"); 38 for(int i=0;i<threadnum;i++) 39 { 40 pData[i].id=i; 41 sprintf(pData[i].name,"yuguoqing"); 42 pData[i].sleep=i*10; 43 hThread[i] = CreateThread(NULL,0,ThreadProc, pData+i,0, dwThreadId+i); 44 } 45 WaitForMultipleObjects(threadnum, hThread, TRUE, INFINITE); 46 return 0; 47 }
信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为每个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:
1 // 信号量对象句柄 2 HANDLE hSemaphore; 3 UINT ThreadProc15(LPVOID pParam) 4 { 5 // 试图进入信号量关口 6 WaitForSingleObject(hSemaphore, INFINITE); 7 // 线程任务处理 8 AfxMessageBox("线程一正在执行!"); 9 // 释放信号量计数 10 ReleaseSemaphore(hSemaphore, 1, NULL); 11 return 0; 12 } 13 UINT ThreadProc16(LPVOID pParam) 14 { 15 // 试图进入信号量关口 16 WaitForSingleObject(hSemaphore, INFINITE); 17 // 线程任务处理 18 AfxMessageBox("线程二正在执行!"); 19 // 释放信号量计数 20 ReleaseSemaphore(hSemaphore, 1, NULL); 21 return 0; 22 } 23 UINT ThreadProc17(LPVOID pParam) 24 { 25 // 试图进入信号量关口 26 WaitForSingleObject(hSemaphore, INFINITE); 27 // 线程任务处理 28 AfxMessageBox("线程三正在执行!"); 29 // 释放信号量计数 30 ReleaseSemaphore(hSemaphore, 1, NULL); 31 return 0; 32 } 33 …… 34 void CSample08View::OnSemaphore() 35 { 36 // 创建信号量对象 37 hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); 38 // 开启线程 39 AfxBeginThread(ThreadProc15, NULL); 40 AfxBeginThread(ThreadProc16, NULL); 41 AfxBeginThread(ThreadProc17, NULL); 42 }
---------------------------------------
图4 开始进入的两个线程
图5 线程二退出后线程三才得以进入
上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于hSemaphore的当前可用资源计数分别为2和1,此时的hSemaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,hSemaphore的当前资源计数减少到0,hSemaphore将不再得到通知,WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出,信号量始终保持了同一时刻不超过2个线程的进入。
MFC中定义的信号量
在MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:
1 CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:
1 // MFC信号量类对象 2 CSemaphore g_clsSemaphore(2, 2); 3 UINT ThreadProc24(LPVOID pParam) 4 { 5 // 试图进入信号量关口 6 g_clsSemaphore.Lock(); 7 // 线程任务处理 8 AfxMessageBox("线程一正在执行!"); 9 // 释放信号量计数 10 g_clsSemaphore.Unlock(); 11 return 0; 12 } 13 UINT ThreadProc25(LPVOID pParam) 14 { 15 // 试图进入信号量关口 16 g_clsSemaphore.Lock(); 17 // 线程任务处理 18 AfxMessageBox("线程二正在执行!"); 19 // 释放信号量计数 20 g_clsSemaphore.Unlock(); 21 return 0; 22 } 23 UINT ThreadProc26(LPVOID pParam) 24 { 25 // 试图进入信号量关口 26 g_clsSemaphore.Lock(); 27 // 线程任务处理 28 AfxMessageBox("线程三正在执行!"); 29 // 释放信号量计数 30 g_clsSemaphore.Unlock(); 31 return 0; 32 } 33 …… 34 void CSample08View::OnSemaphoreMfc() 35 { 36 // 开启线程 37 AfxBeginThread(ThreadProc24, NULL); 38 AfxBeginThread(ThreadProc25, NULL); 39 AfxBeginThread(ThreadProc26, NULL); 40 }