线程同步之信号量
信号量(Semaphore):有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。
比如:以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
分类:
整型信号量(integer semaphore):信号量是整数
记录型信号量(record semaphore):每个信号量s除一个整数值s.value(计数)外,还有一个进程等待队列s.L,其中是阻塞在该信号量的各个进程的标识
二进制信号量(binary semaphore):只允许信号量取0或1值
原理:
信号量(Semaphore)内核对象对线程的同步方式,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
注意点:
1.信号量属于内核对象,与互斥锁一样。运行就慢。
2.Semaphore可以被抽象为五个操作:
-创建 CreateSemaphore(NULL, 1, 5, NULL);
-等待 WaitForSingleObject(g_hSp, INFINITE); 线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。
-释放 ReleaseSemaphore(g_hSp, 1, NULL); 执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。
-销毁 CloseHandle(g_hSp);
3.
函数原型:
CreateSemaphore
(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全控制
LONG lInitialCount,//初始资源数量(每次启动的数目)
LONG lMaximumCount,//最大并发数量(最多可以启动的数目)
LPCWSTR lpName//号量的名称
);
lMaximumCount表示最大并发数量,可以用来设置系统的最大并发数量,如果我们把他的值设为1,lInitialCount也设为1,就是只有一个资源,且每次只能一个线程访问,这样就可以实现线程同步。
4.在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。
ReleaseSemaphore()的函数原型为:
BOOL ReleaseSemaphore
(
HANDLE hSemaphore, // 信号量句柄
LONG lReleaseCount, // 计数递增数量
LPLONG lpPreviousCount // 先前计数
);
该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。
5.
互斥量和信号量的区别
(1). 互斥量用于线程的互斥,信号量用于线程的同步。
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
以上区别是主要想记住的。
note:信号量可以用来实现互斥量的功能
(2). 互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
(3). 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
源代码: // Semaphore.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> #include <process.h> //线程数 #define g_nThreadNum 5 //信号量 HANDLE g_hSp; //累加数 int g_nCount = 0; unsigned _stdcall ThreadFunc(void * lParam) { for (int i = 0; i < 1000000; ++i) { //进入信号量关口 WaitForSingleObject(g_hSp, INFINITE); g_nCount++; //对指定的信号量增加指定的值.释放信号量计数 ReleaseSemaphore(g_hSp, 1, NULL); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { //创建信号量 g_hSp=(HANDLE)CreateSemaphore(NULL, 1, 5, NULL); //启动线程 HANDLE pThread[g_nThreadNum]; for (int i = 0; i < g_nThreadNum; ++i) { pThread[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, 0); if (pThread[i] == 0) { continue; i--; } } //等待线程结束 WaitForMultipleObjects(g_nThreadNum, pThread, TRUE, INFINITE); printf("g_nCount:%d\n", g_nCount); //释放资源 for (int i = 0; i < g_nThreadNum; ++i) { CloseHandle(pThread[i]); } CloseHandle(g_hSp); getchar(); return 0; }