《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“信号量”
“信号量内核对象”用于对资源进行计数。
在信号量内核对象内部,和其他内核对象一样,有一个使用计数,该使用计数表示信号量内核对象被打开的次数。
信号量内核对象中还有两个比较重要的数据,分别表示最大资源数和当前资源数。最大资源数表示能够管理的资源的总数,当前资源数表示目前可以被使用的资源数量。
可以使用CreateSeamphore函数来创建一个信号量内核对象,该函数成功返回句柄,失败返回NULL。
PSECURITY_ATTRIBUTE psa, //安全属性结构指针
LONG lInitialCount, //初始可用资源数
LONG lMaximumCount, //最大资源数
PCTSTR pszName); //信号量内核对象的名字(NULL表示匿名)
在Windows Vista中,提供了一个新的创建信号量内核对象的函数CreateSemaphoreEx,该函数成功返回句柄,失败返回NULL。
PSECURITY_ATTRIBUTES psa, //安全属性结构指针
LONG lInitialCount, //初始可用资源数
LONG lMaximumCount, //最大资源数
PCTSTR pszName, //信号量内核对象的名字(NULL表示匿名)
DWORD dwFlags, //保留型参数,应设置为0
DWORD dwDesiredAccess); //访问限制(参看MSDN)
同样,可以打开一个指定名称的信号量,使用OpenSemaphore函数:
DWORD dwDesiredAccess, //访问限制(参看MSDN)
BOOL bInheritHandle, //是否允许返回的句柄子进程被继承
PCTSTR pszName); //指定的信号量名称
假如,作为一个服务器,有一个缓冲区需要用来存放客户的连接请求,还有一个线程池用来处理连接。但是该缓冲区和线程池的大小有限,比如至多只能同时接纳和处理10位客户的连接请求,而当有10位客户请求连接而尚未处理完成的时候,此时一个新客户也试图建立连接,那么这个连接过程应该被推后,直到有一个连接处理完成之后,这个新客户的连接才能被处理。
这个时候,可以使用信号量机制来处理线程同步的问题。
当服务器初始化的时候,最大资源数为10,没有任何服务器请求连接,可以使用如下代码创建信号量内核对象:
该函数创建了一个信号量内核对象,最大资源数为10,当前可用资源数为0。由于当前可用资源数为0,所以调用WaitForSingleObject等这些等待函数来等待该信号量句柄的线程都会进入等待状态。
这些等待函数在内部会查看信号量内核对象的可用资源数,如果该值大于0,则将其减1,线程保持可调度状态,这些比较和设置可用资源数是以原子过程进行的,所以是线程安全的。
如果可用资源数等于0,则线程进入等待状态,当一个线程将信号量的可用资源数递增之后,某个或某些等待的线程就可以进入就绪状态。
可以调用ReleaseSemaphore函数来让信号量内核对象的可用资源数递增:
HANDLE hSemaphore, //信号量内核对象句柄
LONG lReleaseCount, //可用资源增加个数
PLONG plPreviousCount); //返回上次可用资源的数量,一般传递NULL忽略之
可惜的是,Windows没有提供一种方法让我们仅仅是查询当前信号量的可用资源数。
自己总结了一下信号量使用的模型:
DWORD WINAPI ThreadProc(PVOID pvParam) //线程函数
{
//等待信号量,如果可用资源大于0,递减资源,线程继续运行,否则线程等待
WaitForSingleObject(g_hSem, INFINITE);
//访问资源
//访问完毕,释放,递增可用资源数1个(可以根据需要递增n个)
ReleaseSemaphore(g_hSem, 1, NULL);
}