1 信号量
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。
在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。
信号量内核对象用来对资源计数。与其他所有内核对象相同,它们也包含一个使用计数,但它们还包含另外两个32位值:一个最大资源和一个当前资源计数。
- 最大资源计数表示信号量可以控制的最大资源;
- 当前资源计数表示当前可用资源。
信号量的规则如下:
- 如果当前资源计数大于0,那么信号量处于触发状态
- 如果当前资源计数等于0,那么信号量属于未触发状态
- 系统绝对不会让当前资源计数变为负数
- 当前资源计数绝对不会大于最大资源计数
1.1 描述
以一个停车场的运作为例:
- 假设停车场只有三个车位(共有资源),一开始三个车位都是空的。
- 这时如果同时来了五辆车(线程),看门人(信号量)允许其中三辆(线程)直接进入;
- 然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待;
- 这时,有一辆车(线程)离开停车场,看门人(信号量)得知后,打开车拦,放入外面的一辆进去;
- 如果又离开两辆,则又可以放入两辆,如此往复。
抽象的来讲,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它使得资源被使用了1个);当该整数值为零时,所有试图通过它的线程(车辆)都将处于等待状态。
在信号量上我们定义两种操作: Wait(等待函数) 和 Release(释放函数)。当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于1时。Release(释放)对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了由信号量守护的资源(车位)。
1.2 分类
- 整型信号量(integer semaphore):信号量是整数
- 记录型信号量(record semaphore):每个信号量s,除了一个整数值s.value(计数)外,还有一个进程等待队列s.L
- 二进制信号量(binary semaphore):只允许信号量取0或1值
每个信号量至少须记录两个信息:信号量的值和等待该信号量的进程队列。
1.3 PV操作
PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则S的绝对值表示正在等待使用共享资源的进程数。
◉◉ P操作 申请资源:
1)S减1;
2)若S减1后仍大于等于零,则进程继续执行;
3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
◉◉ V操作 释放资源:
1)S加1;
2)若相加结果大于零,则进程继续执行;
3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。
1.4 说明
信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
在用CreateSemaphore()创建信号量时,即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。
但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不 能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
2 信号量包含的几个操作原语
对信号量有4种操作(#include<semaphore.h>):
- CreateSemaphore() 创建一个信号量
- OpenSemaphore() 打开一个信号量
- ReleaseSemaphore() 释放信号量
- WaitForSingleObject() 等待信号量
信号量Semaphore常用有三个函数,使用很方便。下面是这几个函数的原型和使用说明。
1)CreateSemaphore
-
函数功能:创建信号量。
-
函数原型:
HANDLE CreateSemaphore( LPSECURITY ATTRIBUTES lpSemaphoreAttributes, //安全属性 LONG lInitialCount, //设置信号量的初始计数 LONG lMaximumCount, //设置信号量的最大计数 LPCTSTR lpName //指定信号量对象的名称 );
-
参数说明:
-
返回值
信号量创建成功,将返回该信号量的句柄。
如果给出的信号量名是系统已经存在的信号量,将返回这个已存在信号量的句柄。
如果失败,系统返回NULL,可以调用函数GetLastError()查询失败的原因。
2) OpenSemaphore
-
函数功能:打开信号量,为现有的一个已命名信号机对象创建一个新句柄。和其他核心对象一样,信号量也可以通过名字跨进程访问
-
函数原型:
HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName );
-
函数说明:
3) ReleaseSemaphore
-
函数功能:递增信号量的当前资源计数。
-
函数原型:
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount );
-
函数说明:
5)WaitForSingleObject
-
函数功能:WaitForSingleObject函数用来检测信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
-
函数原型:
等待一个事件 : DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); 等待多个事件 : DWORD WaitForMultipleObjects( DWORD nCount, // 等待句柄数 CONST HANDLE *lpHandles, //指向句柄数组 BOOL bWaitAll, //是否完全等待标志 DWORD dwMilliseconds //等待时间 )
-
函数说明:
-
返回值
执行成功,返回值指示出引发函数返回的事件。它可能为以下值:
5)CloseHandle
信号量的清理与销毁:
- 由于信号量是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。
3 应用和举例
信号量的双用途:互斥与同步
信号量的优点:无忙等
#include <iostream>
#include "windows.h"
using namespace std;
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int number = 1;
HANDLE hSemaphore; //定义信号量句柄
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hSemaphore = CreateSemaphore(
NULL,//指定安全属性,一般传入NULL
1, //指定信号量对象的初始值。该值必须大于等于0
100, //指出该信号量的最大值,该值必须大于0。
NULL//给出信号量的名字。
); //当前1个资源,最大允许100个同时访问
hThread1 = CreateThread(
NULL, //为NULL则表示返回的句柄不能被子进程继承
0, //设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。
FunProc1, //指向线程函数的指针
NULL, //向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
0, //控制线程创建的标志,0:表示创建后立即激活
NULL //保存新线程的id,是指向线程id的指针,如果为空,线程id不被返回
); //函数成功,返回线程句柄,否则返回NULL
hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);//创建后立即激活
if (hThread1 != NULL)CloseHandle(hThread1);
if (hThread2 != NULL)CloseHandle(hThread2);
Sleep(20000); // 让主线程睡眠1秒
if (hSemaphore != NULL)CloseHandle(hSemaphore);
}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
long count;
while (number<25)
{
WaitForSingleObject(hSemaphore, INFINITE);//hSemaphore空闲状态时,申请该信号量
Sleep(1);
cout << "FunProc 1:" << number << endl;
++number;
ReleaseSemaphore(
hSemaphore,//信号量句柄
1, //当前资源计数上加"1"
&count //返回当前资源计数的原始值
);
}
return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
long count;
while (number < 25)
{
WaitForSingleObject(hSemaphore, INFINITE);//hSemaphore空闲状态时,申请该信号量
Sleep(1);
cout << "FunProc 2:" << number << endl;
++number;
ReleaseSemaphore(
hSemaphore,//信号量句柄
1, //当前资源计数上加"1"
&count //返回当前资源计数的原始值
);
}
return 0;
}