9.7 线程同步对象速查表
对象 |
何时处于未触发状态 |
何时处于触发状态 |
成功等待的副作用 |
进程 |
进程仍在运行的时候 |
进程终止的时(ExitProcess、TerminateProcess) |
没有 |
线程 |
线程仍在运行的时候 |
线程终止的时候(ExitThread、TermimateThread) |
没有 |
作业 |
作业尚未超时的时候 |
作业超时的时候 |
没有 |
文件 |
有待处理的I/O请求的时候 |
I/O请求完成的时候 |
没有 |
控制台输入 |
没有输入的时候 |
有输入的时候 |
没有 |
文件变更通知 |
文件没有变更的时候 |
文件系统检测到变更的时候 |
重置通知 |
自动重置事件 |
ResetEvent、PulseEvent或等待成功的时候 |
SetEvent/PulseEvent被调用的时候 |
重置事件 |
手动重置事件 |
ResetEvent、PulseEvent |
SetEvent/PulseEvent被调用的时候 |
没有 |
自动重置可等待计时器 |
CancelWaitableTimer或等待成功的时候 |
时间到的时候(SetWaitableTimer) |
重置计时器 |
手动重置可等待计时器 |
CancelWaitableTimer |
时间到的时候(SetWaitableTimer) |
没有 |
信号量 |
等待成功的时候 |
计数大于0的时候(ReleaseSemaphore) |
计数减1 |
互斥量 |
等待成功的时候 |
不为线程占用的时候(ReleaseMutex) |
把所有权交给线程 |
关键段 (用户模式) |
等待成功的时候 (Try)EnterCriticalSection |
不为线程占用的时候 (LeaveCriticalSection) |
把所有权交给线程 |
SRWLock (用户模式) |
等待成功的时候 (AcuquireSRWLock(Exclusive)) |
不为线程占用的时候 (ReleaseSRWLock(Exclusive)) |
把所有权交给线程 |
条件变量 (用户模式) |
等待成功的时候 (SleepConditionVaiable*) |
被唤醒的时候 (Wake(All)ConditionVariable) |
没有 |
InterLocked* (用户模式) |
从来不会使线程变成不可调度状态,它只是修改一个值并立即返回 |
9.8 其他的线程同步函数
9.8.1 WaitForInputIdle(hProcess,dwMilliseconds)函数
(1)等待进程,直到创建第一个窗口的线程处于输入“空闲”状态时(我的理解是这个线程消息队列中没有键盘和鼠标的消息了,这理解可能不准确!)。当父进程创建子进程时,父进程可以一边继续执行,一边让子进程初始化。这是父进程能够知道子进程己经初始化完毕的唯一方法,就是等待子进程,直到它不再处理任何输入为止。可以调用CreateProcess后,调用WaitForInputIdle。
(2)当要在程序中用模拟用发送键盘消息的方式来打开一个对话框里,也可以用WaitForInputIdle来等待这个对话框。如模拟“Alt+F,O”来打开“打开文件对话框”,可依次发送WM_KEYDOWN(VK_MENU)、WM_KEYDOWN(VK_F)、WM_KEYUP(VK_MENU)、WM_KEYUP(VK_F)、WM_KEYDOWN(VK_O)、WM_KEYUP(VK_O)
,此时系统会创建这个对话框,但由于对话框上可能还要多个子控件,这创建需要一定的时间,所以可以在发送键盘消息后,调用WaitForInputIdle来等待对话框创建完毕。
【WaitForInputIdle程序】模拟发送“Alt+F,0”组合键来打开“打开文件对话框”
#include <windows.h> #include <process.h> #include <locale.h> #include <tchar.h> //模拟按“ALT+F,O”组合键打开“Open Dialog”对话框 int _tmain() { _tsetlocale(LC_ALL, _T("chs")); //根据标题获o取窗体的句柄 HWND hwnd = FindWindow(_T("NotePad"), NULL); if (hwnd){ //通过窗体句柄获取记事本进程ID DWORD dwProcessID; GetWindowThreadProcessId(hwnd, &dwProcessID); //将进程ID转为进程的句柄 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); SetForegroundWindow(hwnd); //这句一定要加上去 //第1种方法 //keybd_event(VK_LMENU, 0, 0, 0); // 按 Alt //keybd_event('F', 0, 0, 0); //keybd_event('F', 0, KEYEVENTF_KEYUP, 0); // F弹上 //keybd_event(VK_LMENU, 0, KEYEVENTF_KEYUP, 0); // Alt弹上 //keybd_event('O', 0, 0, 0); //"O"键按下 //keybd_event('O', 0, KEYEVENTF_KEYUP, 0); // "O"键弹上 //第2种方法 INPUT input[6]; memset(input, 0, sizeof(input)); for (int i = 0; i < 6; i++){ input[i].type = INPUT_KEYBOARD; } //按键顺序 input[0].ki.wVk = input[3].ki.wVk = VK_LMENU; input[1].ki.wVk = input[2].ki.wVk = 'F'; input[4].ki.wVk = input[5].ki.wVk = 'O'; //按键状态(按下或释放) input[2].ki.dwFlags = input[3].ki.dwFlags = input[5].ki.dwFlags = KEYEVENTF_KEYUP; SendInput(6, input, sizeof(INPUT)); if (hProcess){ WaitForInputIdle(hProcess, INFINITE);//等待对话框创建完成 _tprintf(_T("打开对话框%s! "), (GetLastError() == 0) ? _T("成功") : _T("失败")); CloseHandle(hProcess); } } _tsystem(_T("PAUSE")); return 0; }
9.8.2 MsgWaitForMultipleObjects(Ex)函数
(1)当内核对象被触发或当窗口消息被派送到一个由调用线程创建的窗口时都会将调用线程变为可调度状态。即该函数不仅可以等待对象触发,也可以等待指定的消息时触发。
(2)WaitForMultipleObjects会阻塞调用线程,因此以下两种线程:①创建窗口的线程②执行与用户界面相关的任务的线程,不应使用该函数。因为当线程被挂起时,用户在用户界面上的操作将无法得到响应。这时可使用MsgWaitForMultipleObjectEx来替代。
【MsgWaitForMultipleObjects的一般用法】
BOOL bLoop = TRUE; MSG msg; while (bLoop) { DWORD dwRet = MsgWaitForMultipleObjects(1, &hEventOk, FALSE, 10, QS_ALLINPUT); switch (dwRet) { case WAIT_OBJECT_0: //等待对象己触发 ...... //进行相应处理
bLoop = FALSE; //跳出循环 break; case WAIT_OBJECT_0 + 1: //界面消处到达 //从消息队列中获取消息并分派到指定的窗口 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){ TranslateMessage(&msg); DispatchMessage(&msg); } break; case WAIT_TIMEOUT: //超时处理 break; } }
9.8.3 WaitForDebugEvent函数
(1)操作系统内建的调试支持。当调试开始,调试器会被附着到被调试程序。然后只在一边闲着,其内部是通过调用WaitForDebugEvent来等待操作系统通知与被调试程序相关的事件的发生。
(2)当调试器调用这个函数时,会将自己挂起。直到有调试事件时返回。第1个参数指向的结构包含了与刚才发生的调试事件有关的信息。
9.8.4 SignalObjectAndWait函数
(1)会通过一个原子操作来触发一个内核对象并等待另一个内核对象被触发。
(2)函数原型
参数 |
描述 |
hObjectToSignal |
①要触发的内核对象,必须是一个互斥量、信号量或事件。其他任何类型的对象将导致返回WAIT_FAILED,调用GetLastError将得到ERROR_INVALID_HANDLE。 ②函数内部会检查对象类型并分析执行ReleaseMute、ReleaseSemaphore和SetEvent。 |
hObjectToWaitOn |
要等待的内核对象。可以是互斥量、信号量、事件、计时器、进程、线程、作业、控制台输入以及变更通知中的任何一种。 |
dwMilliseconds |
等待最长的时间 |
bAlertable |
是否是可警告的等待。如果是,将允许执行APC队列中的异步函数。 |
返回值 |
WAIT_OBJECT_0:等待的对象被触发 WAIT_TIMEOUT:超时 WAIT_FAILED:等待失败,如第1个参数设置错误 WAIT_ABANDONED:等待的对象被其他线程废弃 WAIT_IO_COMPLETION:用户模式下的一个或多个APC异步函数调用结束。 |
(3)函数的特点
①一个函数完成触发一个对象并等待另一个对象的两个操作,可以节省时间。
②上述两个操作是以原子的方式进行的。
错误的代码 |
正常的代码 |
【工作线程的代码】 ……//执行一些工作 SetEvent(hEventWorkerThreadDone); ① Wait*(hEventMoreWorkerToBeDone,INFINITE);② ……//做其他的工作 【另一线程的代码】 Wait*(hEventWorkerThreadDone);③ PulseEvent(hEventMoreWorkToBeDone);④ |
【工作线程的代码的改写】 ……//执行一些工作 SignalObjectAndWait(hEventWorkerThreadDone, hEventMoreToBeDone,INFINITE,FALSE); ① ……//做其他的工作 【另一线程代码不变】 |
说明: A、代码期望的执行顺序是①→②→③→④。如果这样执行则工作正常。 B、但在执行①时,会唤醒第2个线程。此时如果顺序是先①且未执行完→③→④→①剩作部分→②。当执行④时会发现没有等待相应对象的线程,就会直接返回。这时工作线程的Wait*就会错过hEventMoreWorkToBeDone被触发的机会,这以后再进入Wait*,所以就没办法再被唤醒。 C、注意PulseEvent的工作特点是先触发对象并唤醒等待线程,再将对象重置为未触发状态。 |
说明:左边代码的第①、②两步实际上就是执行先触发一个对象,再等待另一个对象的操作。而SignalObjectAndWait是以原子方式来操作触发和等待两个动作的。所以当另一个线程被唤醒的时候,可以百分百的确定工作线程正在等待hEventMoreWorkToBeDone事件。因此当另一个线程的脉冲事件被触发时,工作线程一定能够收到。 |
9.8.5 使用等待链遍历API来检测死锁
(1)Vista提供的等待链遍历(Wait Chain Traversal,WCT)API
WCF所记录的同步机制的类型
可能的锁 |
描述 |
关键段 |
Windows会记录哪个线程正在占用哪个关键段 |
互斥量 |
Windows会记录哪个线程正在占用哪个互斥量。即便是被遗弃的也不例外。 |
进程和线程 |
Windows会记录哪个线程正在等待进程终止或线程终止 |
SendMessage调用 |
记录哪个线程正在等待SendMessage调用返回 |
COM初始化和调用 |
Windows会记录对CoCreateInstance的调用及对COM对象的方法的调用 |
高级本地调用 (ALPC) |
在Windows Vista中,新的内核进程间通信ALPC取代本地过程调用(LPC) |
注意:WCT并不记录SRWLock、事件、信号量及可等待计时器,因为任何线程在任一时刻都可以触发任意多个的此类对象,从而唤醒被阻塞的线程。
(2)死锁的检测
①打开WCT会话:使用OpenThreadWaitChainSession函数
②如果需要检测COM:使用RegisterWaitChainCOMCallback
③取得等待链的信息:使用GetThreadWaitChain函数
④关闭等待链:CloseThreadWaitChainSession函数
(3)相关函数
①OpenThreadWaitChainSession函数
参数 |
描述 |
dwFlags |
0表示同步,WCT_ASYNC_OPEN_FLAG表示异步 |
callback |
异步的话要设置该回调函数指针 |
返回值 |
成功时,返回HWCT类型的句柄。失败返回NULL |
②GetThreadWaitChain函数:取得等待链信息
参数 |
描述 |
hWctSession |
由OpenThreadWaitChainSession返回的句柄 |
pContext |
异步方法中要传给回调函数的额外参数 |
dwFlags |
希望获得该线程的哪些等待信息 ①WCT_OUT_OF_PROC_FLAG(0x1):包含与当前进程之外的其他进程有关的节点信息。一般在创建多进程的系统中。 ②WCT_OUT_OF_PROC_CS_FLAG(0x4):收集当前进程之外的其他进程的关键段信息。(多进程的系统中) ③WCT_OUT_OF_PROC_COM_FLAG(0x2):如果用到MTA_COM服务器,则设置该标志 ④WCTP_GETINFO_ALL_FLAGS:以上所有标志的集合 |
TID |
要查询的线程的线程ID |
pNodeCount |
返回值,指明等待链中的结点个数 |
pNodeInfoArray |
返回值,等待链结点信息,每个元素是个WAITCHAIN_NODE_INFO结构体 |
pbIsCycle |
返回值,TRUE表示检测到死锁 |
★★WAITCHAIN_NODE_INFO结构体
A、结构体
B、各字段说明
字段 |
描述 |
ObjectType (结点对象类型) |
①WctThreadType:表示该节点是一个“阻塞”状态的线程,此时第2个字段ObjectStatus字段描述了线程的状态。 ②WctCriticalSectionType:占用的对象是一个关键段 ③wctSendMessageType:阻塞在SendMessage调用 ④WctMutexType:占用的对象是一个互斥量 ⑤WctAlpcType:阻塞在一个ALPC调用 ⑥WctComType:正在等待一个COM调用返回 ⑦WctThreadWaitType :正在等待一个线程结束 ⑧WctProcessWaitType:正在等待一个进程终止 ⑨WctComActivationType:正在等待一个CoCreateInstance调用返回 ⑩WctUnknownType:用于今后对API进行扩展的占位符 |
ObjectStatus (结点对象状态) |
①WctStatusNoAccess=1:访问该对象是被拒绝 ②WctStautsRunning、WctStatusBlocked、WctStatusPidOnly、 WctStatusPidOnlyRpcss:线程状态,是线程独有的。在ObjectType为 WctThreadType时,ObjectStatus状态) ③WctStatusOwned、WctStatusNotOwned、WctStatusAboundoned:节点相对应的锁的状态:被持有的、未被持有、放弃的。即ObjectType不为WctThreadType时。 ④WctStatusUnknown、WctStatusError:所有对象共有的状态 ⑤WctStatusMax |
联合体 |
当ObjectType为wctThreadType时,ThreadObject成员有效。否则LockObject有效。 LockObject:包括对象名字、超时(时间)、可警告状态等。 ThreadObject:(包含进程ID、线程ID、等待时间、线程上下文) |
★★★小结:等待链提供的信息:
A、第1个节点:表示线程本身(如运行状态Runing或Blocked)
B、第2个节点:该线程正在等待什么东西(如锁、进/线程、COM调用等等)
C、第3个节点:这个锁被哪个线程挂有?
D、第4个节点:挂有这个锁的线程又在等什么东西?周而复始的查下去……
③CloseThreadWaitChainSession:关闭WCT句柄。
④RegisterWaitChainCOMCallback函数
参数 |
描述 |
CallStateCallback |
CoGetCallState函数的地址 |
ActivationStateCallback |
CoGetActivationState函数地址 |
【调用RegisterWaitChainCOMCallback的代码示例】
PCOGETCALLSTATE CallStateCallback; //函数指针 PCOGETACTIVATIONSTATE ActivationStateCallback; //函数指针 HMODULE hOLE32DLL = LoadLibrary(TEXT("OLE32.DLL"));//加载OLE32.DLL //从ole32.dll中取得函数地址 CallStateCallback = (PCOGETCALLSTATE) GetProcAddress(_hOLE32DLL, "CoGetCallState"); ActivationStateCallback = (PCOGETACTIVATIONSTATE) GetProcAddress(_hOLE32DLL, "CoGetActivationState"); //在等待链中注册COM函数 RegisterWaitChainCOMCallback(CallStateCallback,ActivationStateCallback);
【LockCop示例程序】死锁的检测
//LockCop.cpp(主程序)
/************************************************************************ Module: LockCop.cpp Notices:Copyright(c) 2008 Jeffrey Richter& Christophe Nasarre ************************************************************************/ #include "../../CommonFiles/CmnHdr.h" #include "../../CommonFiles/ToolHelp.h" #include "resource.h" #include "ChainParser.h" #include <tchar.h> #include <strsafe.h> ////////////////////////////////////////////////////////////////////////// HANDLE g_hInstance; HWND g_hDlg; ////////////////////////////////////////////////////////////////////////// #define DETAILS_CTRL GetDlgItem(g_hDlg,IDC_EDIT_DETAILS) //将字符串增加到编辑框中 void AddText(PCTSTR pszFormat, ...) { va_list argList; va_start(argList, pszFormat); TCHAR sz[20 * 1024]; Edit_GetText(DETAILS_CTRL, sz, _countof(sz)); _vstprintf_s(_tcschr(sz,TEXT('