9.5 信号量内核对象(Semaphore)
(1)信号量的组成
①计数器:该内核对象被使用的次数
②最大资源数量:标识信号量可以控制的最大资源数量(带符号的32位)
③当前资源数量:标识当前可用资源的数量(带符号的32位)
(2)信号量的使用规则
①如果当前资源计数>0,那么信号量处于触发状态,表示有可用资源。
②如果当前资源计数=0,那么信号量处于未触发状态,表示没有可用资源。
③系统绝不会让当前资源计数变为负数;
④当前资源计数绝对不会大于最大资源计数
(3)信号量的用法
(4)相关函数
①创建信号量CreateSemaphore
参数 |
描述 |
psa |
安全属性 |
lInitialCount |
初始化时,共有多少个资源是可用的。如果该值设为0,表示没有可用资源,此时信号量为未触发状态,任何等待信号数的线程将进入等待状态。以后可通过调用ReleaseSemaphore来增加可用资源,同时变为触发状态。 |
LMaximumCount |
能够处理的最大的资源数量 |
pszName |
信号量的名称 |
②增加信号量:ReleaseSemaphore
参数 |
描述 |
hSemaphore |
信号量句柄 |
lReleaseCount |
将lReleaseCount值加到信号量的当前资源计数上 |
pLPreviousCount |
返回当前资源计数的原始值,一般填NULL |
★调用ReleaseSemaphore可以获得资源计数的原始值,但同时会增加当前资源的计数值。目前还没有办法在不改变当前资源计数值的前提下获得信号量的可用资源数。(即使lReleaseCount填入0也获取不到!)
③Wait*之类的等待函数:当线程调用等待函数时,如果信号量处于触发状态(可用资源大于0),则线程获得信号量的一个资源并把可用资源数量减1,同时继续执行。如果信号量处于未触发状态,则线程进入等待状态。直到其他线程释放资源。
【Semaphore程序】
#include <windows.h> #include <tchar.h> #include <locale.h> ////////////////////////////////////////////////////////////////////////// //一个只能容纳10个客人的餐馆来了12个客户 #define MAX_SEM_COUNT 10 #define THREADCOUNT 12 ////////////////////////////////////////////////////////////////////////// HANDLE g_hSemaphore = NULL; DWORD WINAPI ThreadProc(LPVOID pvParam); ////////////////////////////////////////////////////////////////////////// int _tmain() { _tsetlocale(LC_ALL, _T("chs")); HANDLE aThread[THREADCOUNT] = { NULL }; DWORD dwThreadID = 0; g_hSemaphore = CreateSemaphore(NULL, MAX_SEM_COUNT, MAX_SEM_COUNT, NULL); //以下两句与上面那句的实现的效果一样,先创建无资源的信号量,再增加资源 //g_hSemaphore = CreateSemaphore(NULL, 0, MAX_SEM_COUNT, NULL); //ReleaseSemaphore(g_hSemaphore, MAX_SEM_COUNT, NULL); if (NULL == g_hSemaphore){ _tprintf(_T("创建信号量失败:%d\n"), GetLastError()); return -1; } //创建12个线程 for (int i = 0; i < THREADCOUNT;i++){ aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL,0,&dwThreadID); if (aThread[i] == NULL){ _tprintf(_T("创建线程失败:%d\n"), GetLastError()); return -1; } } //等待所有线程结束 WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE); //关闭所有子线程 for (int i = 0; i < THREADCOUNT;i++){ CloseHandle(aThread[i]); } //关闭信号量 CloseHandle(g_hSemaphore); _tsystem(_T("PAUSE")); return 0; } //线程函数 DWORD WINAPI ThreadProc(LPVOID pvParam) { DWORD dwWaitResult; BOOL bContinue = TRUE; LONG lPrevCount=0; while (bContinue){ //等待资源,立即返回 dwWaitResult = WaitForSingleObject(g_hSemaphore, 0L); switch (dwWaitResult) { case WAIT_OBJECT_0: bContinue = FALSE; _tprintf(_T("线程%d:等待成功!\n"), GetCurrentThreadId()); Sleep(20); if (!ReleaseSemaphore(g_hSemaphore, 1, &lPrevCount)){ _tprintf(_T("ReleaseSemaphore失败:%d!\n"), GetLastError()); } //_tprintf(_T("线程%d:等待成功,当前可用资源%d!\n"), GetCurrentThreadId(),lPrevCount+1); break; case WAIT_TIMEOUT: _tprintf(_T("线程%d:等待超时!\n"), GetCurrentThreadId()); break; } } _tprintf(_T("线程%d:退出\n"), GetCurrentThreadId()); return 0; }
9.6 互斥量内核对象(Mutex)
(1)互斥量的组成
①使用计数:与其他内核对象,都有一个使用计数
②线程ID:标识当前占用这个互斥量是哪个线程
③递归计数:该线程占用互斥量的次数
(2)互斥量的规则
①如果线程ID为0(即无效ID),那么该互斥量不为任何线程所占,即处于触发状态
②如果线程ID为非零值,表示该线程己经占用该互斥量,它处于非触发状态。
③其他内核对象只会记录哪些线程正在等待,但互斥量还会记录哪个线程等待成功。这使得它在未被触发的时候,也可以被线程所获得。当任何线程试图调用ReleasMutex时,该函数会检查调用线程的ID与互斥量内部保存的ID是否一致。如果一致,则递归计数递减。否则返回FALSE,这时调用GetLastError将得到ERROR_NOT_OWNER。
(3)互斥量的使用方法
①创建互斥量:CreateMutext(psa,bInitialOwner,pszName);其中bInitialOwner为TRUE时表示将调用线程ID设为互斥量内部的线程ID,递归计数加1。否则表示互斥量不被占用(处于触发状态)
②释放互斥量:ReleaseMutex,递归计数减1,当递归计数为0时,将线程ID设为0。这里互斥量被触发。
9.6.1 互斥量被“遗弃问题”
如果占用互斥量的线程在释放互斥量之前提前终止,这种现象叫互斥量被“遗弃”(Abandoned),系统会自动将该互斥量的线程ID和递归计数设为0。再“公平地”唤醒正在等待的一个线程,但这时唤醒的线程从Wait*中返回的不再是WAIT_OBJECT_0,而是返回WAIT_ABANDONED,以表示该线程等到了一个被遗弃的互斥量。所以此时被互斥量保护的资源处于什么状态,这个被唤醒的线程并不知道,要求应用程序自己决定怎么做。
9.6.2 互斥量与关键段的比较
特征 |
互斥量 |
关键段 |
性能 |
慢 |
快 |
是否能跨进程使用 |
是 |
否 |
声时 |
HANDLE hmtx |
CRITICAL_SECTION cs; |
初始化 |
CreateMutex |
InitializeCriticalSection |
清理 |
CloseHandle |
DeleteCriticalSection |
无限等待 |
Wait*(hmtx,INFINITE) |
EnterCriticalSection(&cs) |
0等待 |
Wait*(hmtx,0) |
TryEnterCriticalSection |
任意时长等待 |
Wait*(htmx,dwMilliseconds) |
不支持 |
释放 |
ReleaseMutex |
LeaveCriticalSection |
是否能同时等待其他内核对象 |
是 如WaitForMultipleObject |
否 |