DWORD WaitForSingleObject(
_In_ HANDLE hHandle, //内核对象句柄
_In_ DWORD dwMiliseconds //等待超时时间(微秒,INFI)
);
/******
***return
WAIT_OBJECT_0: 成功返回
WAIT_TIMEOUT: 超时返回
WAIT_FAILED: 传入的参数错误
*/
在创建线程时使用等待函数后,此函数会在等待超时或线程结束时返回,因此我们的主线程一次只能启动一个线程,从而避免上述例子中多线程访问同一个数据时所引发的问题.
与WaitForSingleObject类似,唯一的不同之处在于它允许调用线程同时检查多个内核对象的触发状态
DWORD WaitForMultipleObjects(
DWORD dwCount, // 检查的内核对象的数量
CONST HANDLE* phObjects, // 内核对象句柄数组
BOOL bWaitAll, // 是否在所有内核对象触发之后返回
DWORD dwMilliseconds) // 等待时间
/***********
* return
WAIT_FAILED
WAIT_TIMEOUT
// 如果 bWaitAll 是 TRUE ,则返回 WAIT_OBJECT_0
// 如果 bWaitAll 是 FALSE, 则返回值是 WAIT_OBJECT_0和(WAIT_OBJECT_0 + dwCount - 1) 之间的任意一个值,内核对象数组的下标为:返回值 - WAIT_OBJECT_0
*/
对一些内核对象来说,成功地调用 WaitForSingleObject 与 WaitForMultipleObjects 事实上会改变对象的状态。如:自动重置事件内核对象,当时间对象被触发的时候,函数会检测到这一情况,这时它可以直接返回 WAIT_OBJECT_0 给调用线程。但是,就在函数返回之前,它会使事件变为非触发状态——这就是等待成功所引起的副作用。 WaitForMultipleObjects是以原子方式工作的,当函数检查内核对象的状态时,任何其他线程都不能在背后修改对象的状态。
事件(Event) 是在线程同步中最常使用的一种同步对象,而且比其他对象要简单一些。像其他对象一样,事件包含了一个使用计数,一个是用来标识自动重置/手动重置的BOOL值,另一个是表示事件有没有触发的BOOL值
/*关键函数*/
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,//属性
BOOL hManualReset, //手工重置
BOOL bInitialState, //初始状态
LPCTSTR lpName //事件对象名称
);
//注:非手工状态下,调用SetEvent放行一个线程后,会自动再次设为无
//信号状态,直到再次调用SetEvent
/*设置标记为有信号状态(释放等待函数)*/
BOOL SetEvent(
HANDLE hEvent //事件对象句柄
);
/*重置标记为无信号状态(阻塞等待函数)*/
BOOL WINAPI ResetEvent(
HANDLE hEvent //事件对象句柄
);
//打开内核对象
HANDLE OpenEvent(
DWORD dwDesiredAccess,//对事件对象的请求访问权限
BOOL bInheritHandle,//是否能继承
LPCTSTR lpName //事件对象的名字
);
一个防多开的例子
if (!OpenEvent(EVENT_MODIFY_STATE, TRUE,L"Global\\Text"))
CreateEvent(NULL, TRUE, TRUE, L"Global\\Text");
else
return 0;
示例:
int g_nNum = 0;
HANDLE g_hEventA = nullptr;
HANDLE g_hEventB = nullptr;
DWORD WINAPI ThreadProcA(LPVOID lpParam) {
for (int i = 0; i < 5; i++){
WaitForSingleObject(g_hEventA, INFINITE);
ResetEvent(g_hEventB);
printf("%d ", g_nNum++);
SetEvent(g_hEventB);
}
return 0;
}
DWORD WINAPI ThreadProcB(LPVOID lpParam){
for (int i = 0; i < 5; i++){
WaitForSingleObject(g_hEventB, INFINITE);
ResetEvent(g_hEventA);
printf("%d ", g_nNum++);
SetEvent(g_hEventA);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (!(g_hEventA = CreateEvent(NULL, TRUE, TRUE, NULL))) return 0;
if (!(g_hEventB = CreateEvent(NULL, TRUE, FALSE, NULL))) return 0;
CreateThread(NULL, 0, ThreadProcA, NULL, 0, nullptr);
CreateThread(NULL, 0, ThreadProcB, NULL, 0, nullptr);
system("pause");
return 0;
}
可等待的计时器(Waitable Timer): 会在某个指定的时间触发,或每隔一段时间触发一次。
创建可等待的计时器:
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
PCTSTR pszName);
打开获取一个已经存在的可等待计时器的句柄:
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
在创建可等待的计时器的时候,计时器总是处于未触发状态。等我们想要触发计时器的时候必须调用 SetWaitableTimer 函数:
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER *pDueTime, // 表示计时器第一次触发的时间
LONG lPeriod, // 在第一次触发之后,多长时间触发一次
PTIMERAPCROUTINE pfnCompletionRoutine, // 计时器函数
PVOID pvArgToCompletionRoutine, // 传入参数
BOOL bResume); // 是否支持挂起与恢复
取消计时器:
BOOL CancelWaitableTimer(HANDLE hTimer); //取消一个计时器
//第一次触发时间为2008年1月1日下午1:00,之后每隔6小时触发一次
HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal, ftUTC;
LARGE_INTEGER liUTC;
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
st.wYear = 2008;
st.wMonth = 1;
st.wDayOfWeek = 0; //忽略
st.wDay = 1;
st.wHour = 13;
st.wMinute = 0;
st.wSecond = 0;
st.wMilliseconds = 0;
SystemTimeToFileTime(&st, &ftLocal);
// 将本地时间转换为 UTC 时间
LocalFileTimeToFileTime(&ftLocal, &ftUTC);
// 将 FILETIME 转换为 LARGE_INTEGER , FILETIME 与 LARGE_INTEGER 二进制格式一致,但是前者是地址是32为边界,后者是64位边界
liUTC.LowPart = ftUTC.dwLowDateTime;
liUTC.HighPart = ftUTC.dwHighDateTime;
SetWaitableTimer(hTimer, &liUTC, 6*60*60*1000,
NULL, NULL, FALSE);
// 触发一次就不再触发的计时器,即给 lPeriod参数传入0就可以了。
// 异步过程调用原型
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
FILETIME ftUTC, ftLocal;
SYSTEMTIME st;
TCHAR szBuf[256];
ftUTC.dwLowDateTime = dwTimerLowValue;
ftUTC.dwHighDateTime = dwTimerHighValue;
FileTimeToLocalFileTime(&ftUTC, &ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE,
&st, NULL, szBuf, _countof(szBuf));
_tcscat_s(szBuf, _countof(szBuf), TEXT(" "));
GetTimeFormat(LOCALE_USER_DEFAULT, 0,
&st, NULL, _tcschr(szBuf, TEXT('\0')),
(int)(_countof(szBuf) - _tcslen(szBuf)));
//Show the time to the user
MessageBox(NULL, szBuf, TEXT("Timer went off at..."), MB_OK);
}
void SomeFunc() {
// Create a timer. (It doesn't matter whether it's manual-reset
// or auto-reset.)
HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
LARGE_INTEGER li = {0};
SetWaitableTimer(hTimer, &li, 5000, TimerAPCRoutine, NULL, FALSE);
SleepEx(INFINITE, TRUE);
CloseHandle(hTimeer);
}
SetWaitableTimer线程必须是由于调用 SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx 或 SignalObjectAndWait 而进入等待状态,APC异步调用才会被调用。
信号量关键函数:
/*创建信号量*/
HANDLE WINAPI CreateSemaphore(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//属性
_In_ LONG lInitialCount, //信号初始值
_In_ LONG lMaximumCount,//信号最大值
_In_opt_ LPCTSTR lpName //信号量名称
);
/*释放信号量*/
BOOL ReleaseSemaphore(
HANDLE hSemaphore, //信号量句柄
LONG lReleaseCount, //释放的信号量数量
LPLONG lpPreviousCount //返回信号量上次值
);
/*打开信号量*/
HANDLE WINAPI OpenSemaphore(
DWORD dwDesiredAccess, //对信号量的请求访问权限
BOOL bInheritHandle, //是否允许子进程继承此句柄
LPCTSTR lpName //信号量名称
);
示例
int g_nNum = 0;
HANDLE g_hSemaphore = nullptr;
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
for (int i = 0; i < 5; i++)
{
WaitForSingleObject(g_hSemaphore, INFINITE);
printf("%d", g_nNum++);
ReleaseSemaphore(g_hSemaphore, 1, NULL);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (!(g_hSemaphore=CreateSemaphore(NULL,0,1,NULL)) )
{
return 0;
}
CreateThread(NULL, 0, ThreadProc, NULL, 0, nullptr);
CreateThread(NULL, 0, ThreadProc, NULL, 0, nullptr);
system("pause");
return 0;
}
互斥对象是一个非常简单的多线程同步内核对象,如果一个信号量未被线程所拥有(被等待函数获取),那么它是”有信号状态(非阻塞)”,只要它被线程获取,那么它就会变成”无信号状态(阻塞)”,需要注意的是,单一互斥对象只对同一线程有效,以下是互斥对象的一些常用API
//创建互斥对象
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //属性
BOOL bInitialOwner, //初始状态
LPCTSTR lpName //互斥对象名称
);
//释放互斥对象
BOOL ReleaseMutex(HANDLE hMutex); //互斥对象句柄
//打开互斥对象
HANDLE OpenMutex(
DWORD dwDesiredAccess, //对互斥对象的请求访问权限
BOOL bInheritHandle,//是否希望子进程能够继承句柄
LPCTSTR lpName //互斥对象名称
);
如果线程成功地等待了互斥量对象不止一次,那么线程必须调用 ReleaseMutex 相同的次数才能使对象递归计数变成0.当递归计数变成0的时候,函数还会将线程ID设为0,这样就触发了对象。
互斥对象—示例
int g_nNum = 0;
HANDLE g_hMutex = nullptr;
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
for (int i = 0; i < 5; i++)
{
WaitForSingleObject(g_hMutex,INFINITE);
printf("%d",g_nNum++);
ReleaseMutex(g_hMutex);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (!(g_hMutex=CreateMutex(NULL, FALSE,NULL)))
return 0;
CreateThread(NULL, 0, ThreadProc, NULL, 0, nullptr);
CreateThread(NULL, 0, ThreadProc,NULL, 0, nullptr);
system("pause");
return 0;
}
互斥与临界区的比较:
特征 | 互斥量 | 临界区 |
---|---|---|
性能 | 慢 | 快 |
是否能跨进程使用 | 是 | 否 |
声明 | HANDLE hmtx; | CRITICAL_SECTION cs; |
初始化 | hmtx = CreateMutex(NULL,FALSE,NULL); | InitializeCriticalSection(&cs); |
清理 | CloseHandle(hmtx); | DeleteCriticalSection(&cs) |
无限等待 | WaitForSingleObject(hmtx,INFINITE); | EnterCriticalSection(&cs); |
0等待 | WaitForSingleObject(hmtx,0); | TryEnterCriticalSection(&cs); |
任意时间长度等待 | WaitForSingleObject(hmtx, dwMilliseconds); | 不支持 |
释放 | ReleaseMutex(hmtx); | LeaveCriticalSection(&cs); |
是否能同时等待其他内核对象 | WaitForMultipleObjects | 否 |
对象 | 何时处于未触发状态 | 何时处于触发状态 | 成功等待的副作用 |
---|---|---|---|
进程 | 进程仍在运行 | 进程终止时(ExitProcess,TerminateProcess) | 无 |
线程 | 线程仍在运行 | 线程终止时(ExitThread,TerminateThread) | 无 |
作业 | 作业尚未超时的 | 作业超时时 | 无 |
文件 | 有待处理的IO请求时 | IO请求完成时 | 无 |
控制台输入 | 没有输入时 | 有输入时 | 无 |
文件变更通知 | 文件没有变更时 | 文件系统检测到变更的时候 | 重置通知 |
自动重置事件 | ResetEvent,PulseEvent或等待成功的时候 | SetEvent/PulseEvent被调用的时候 | 重置事件 |
手动重置事件 | ResetEvent,PulseEvent | SetEvent/PulseEvent被调用的时候 | 没有 |
自动重置可等待计时器 | CancelWaitableTimer或等待成功时 | 时间到的时候(SetWaitableTimer) | 重置计时器 |
手动重置可等待计时器 | CancelWaitableTimer | 时间到的时候(SetWaitableTimer) | 没有 |
信号量 | 等待成功的时候 | 计数大于0的时候(ReleaseSemaphore) | 计数减1 |
互斥量 | 等待成功的时候 | 不为线程占用的时候(ReleaseMutex) | 把所有权交给线程 |
临界区(用户模式) | 等待成功的时候((Try)EnterCriticalSection) | 不为线程占用的时候(LeaveCriticalSection) | 把所有权交给线程 |
SRWLock(用户模式) | 等待成功的时候(AcquireSRWLock(Exclusive)) | 不为线程占用的时候(ReleaseSRWLock(Exclusive)) | 把所有权交给线程 |
条件变量(用户模式) | 等待成功的时候(SleepConditionVariable*) | 被唤醒的时候(Wake(All)ConditionVariable) | 没有 |
DWORD WaitForInputIdle(
HANDLE hProcess,
DWORD dwMilliseconds);
可能的锁 | 描述 |
---|---|
临界区 | Windows会记录哪个线程正在占用哪个临界区 |
互斥量 | Windows会记录哪个线程正在占用哪个互斥量。即便已经被遗弃的互斥量 |
进程和线程 | Windows会记录哪个线程正在等待进程终止或线程终止 |
SendMessage调用 | 知道哪个线程正在等待SendMessage调用返回 |
COM初始化和调用 | Windows会记录对CoCreateInstance的调用以及对COM对象的方法的调用 |
高级本地过程调用(Advanced Local Procedure Call,ALPC) | 本地过程调用 |