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!\n"), (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('\0')),_countof(sz) - _tcslen(sz), pszFormat,argList); Edit_SetText(DETAILS_CTRL, sz); va_end(argList); } ////////////////////////////////////////////////////////////////////////// void OnRefreshProcess() //刷新进程组合框 { HWND hwndList = GetDlgItem(g_hDlg, IDC_COMBO_PROCESS); SetWindowRedraw(hwndList, FALSE); //禁止刷新 ComboBox_ResetContent(hwndList); //清空 CToolhelp thProcesses(TH32CS_SNAPPROCESS); //进程快照 PROCESSENTRY32 pe = { sizeof(pe) }; BOOL fOk = thProcesses.ProcessFirst(&pe); for (; fOk;fOk=thProcesses.ProcessNext(&pe)){ TCHAR sz[1024]; //进程名称(去路径)和ID显示在列表框中 //_tcsrchr:查找最后一个\出现的位置 PCTSTR pszExeFile = _tcsrchr(pe.szExeFile, _T('\\')); // if (pszExeFile==NULL){ pszExeFile = pe.szExeFile; } else pszExeFile++; //跳过\,指定文件名开始的地方。 StringCchPrintf(sz, _countof(sz), TEXT("%04u - %s"), pe.th32ProcessID, pszExeFile); int n = ComboBox_AddString(hwndList, sz); //关联进程ID到列表框项中 ComboBox_SetItemData(hwndList, n, pe.th32ProcessID); } ComboBox_SetCurSel(hwndList, 0); //选中第1个项 //模拟用户选择第1项,该操作会将该进程锁的情况显示在下面的编辑框中 FORWARD_WM_COMMAND(g_hDlg, IDC_COMBO_PROCESS, hwndList, CBN_SELCHANGE, SendMessage); SetWindowRedraw(hwndList, TRUE); //开始刷新 InvalidateRect(hwndList, NULL, FALSE); } ////////////////////////////////////////////////////////////////////////// void OnUpdateLocks() { SetWindowText(DETAILS_CTRL, TEXT("")); //清空编辑框内容 //从组合框中获得当前进程ID HWND hwndCtl = GetDlgItem(g_hDlg, IDC_COMBO_PROCESS); DWORD dwSelection = ComboBox_GetCurSel(hwndCtl); DWORD PID = (DWORD)ComboBox_GetItemData(hwndCtl, dwSelection); AddText(TEXT("进程[%u]中的线程\r\n"), PID); CChainParser parser(DETAILS_CTRL); parser.ParseThreads(PID); } ////////////////////////////////////////////////////////////////////////// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_LOCKCOP); //保存主窗口句柄 g_hDlg = hwnd; //使用等宽字体来显示锁的详细信息 //SetWindowFont(GetDlgItem(hwnd, IDC_EDIT_DETAILS), // GetStockFont(ANSI_FIXED_FONT),FALSE); //填充进程组合框 OnRefreshProcess(); return TRUE; } void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity) { switch (id) { case IDCANCEL: //用户按了确定按钮或ESC键 EndDialog(hwnd, id); break; case IDC_COMBO_PROCESS: if (codeNotity ==CBN_SELCHANGE) OnUpdateLocks(); break; case IDC_BTN_REFRESH: //刷新组合框 OnRefreshProcess(); break; case IDC_BTN_UPDATE: OnUpdateLocks(); break; } } INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); } return FALSE; } ////////////////////////////////////////////////////////////////////////// int APIENTRY _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) { //保存模块句柄 g_hInstance = hInstExe; //提升程序的Debug权限 CToolhelp::EnablePrivilege(SE_DEBUG_NAME, TRUE); //显示主窗口 DialogBox(hInstExe, MAKEINTRESOURCE(IDD_LOCKCOP), NULL, Dlg_Proc); CToolhelp::EnablePrivilege(SE_DEBUG_NAME, FALSE); return 0; }
//WaitChainTraversal.h——自定义的,用于遍历等待链的类
/************************************************************************* Module:WaitChainTransversal.h Details:Helpers class for the "Wait Chain Transveral" API Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre *************************************************************************/ #pragma once ////////////////////////////////////////////////////////////////////////// #include "CmnHdr.h" #include "ToolHelp.h" #include <wct.h> #include <tchar.h> ////////////////////////////////////////////////////////////////////////// class CWCT { public: CWCT(); ~CWCT(); //枚举给定进程中的线程,并转储他们的等待链 void ParseThreads(DWORD PID); protected: //在对某个线程进行分析时,会调用一次OnThread函数 //TID为线程ID,bDeadlock该线程是否被死锁,nodeCount等待链中节点的数量 virtual void OnThread(DWORD TID, BOOL bDeadlock, DWORD nodeCount); //用于显示等待链中每个结点的信息 virtual void OnChainNodeInfo(DWORD rootTID, DWORD currentNode, WAITCHAIN_NODE_INFO nodeInfo); //返回当前线程等待链中节点的数量 DWORD GetNodesInChain(); //返回当前被分析进程的ID DWORD GetPID(); private: void InitCOM(); void ParseThread(DWORD TID); private: //WCT会话的句柄 HWCT _hWCTSession; //OLE32.DLL模块的句柄 HMODULE _hOLE32DLL; DWORD _PID; DWORD _dwNodeCount; }; ////////////////////////////////////////////////////////////////////////// CWCT::CWCT(){ _hOLE32DLL = NULL; //用同步的方式打开WCT会话 //0表示同步,WCT_ASYNC_OPEN_FLAG表示异步 _hWCTSession = OpenThreadWaitChainSession(0, NULL); if (_hWCTSession == NULL) return; //可以检测COM调用中的死锁 InitCOM(); } ////////////////////////////////////////////////////////////////////////// CWCT::~CWCT(){ //别忘了御载OLE32.DLL if (_hOLE32DLL != NULL) FreeLibrary(_hOLE32DLL); //关闭WCT会话 if (_hWCTSession != NULL) CloseThreadWaitChainSession(_hWCTSession); } ////////////////////////////////////////////////////////////////////////// void CWCT::InitCOM(){ PCOGETCALLSTATE CallStateCallback; //CoGetCallState回调函数 PCOGETACTIVATIONSTATE ActivationStateCallback;//CoGetActivationState回调函数 //载入OLE32.DLL到进程中,以便获得以上两个回调函数的地址 _hOLE32DLL = LoadLibrary(TEXT("OLE32.DLL")); CallStateCallback = (PCOGETCALLSTATE) GetProcAddress(_hOLE32DLL, "CoGetCallState"); ActivationStateCallback = (PCOGETACTIVATIONSTATE) GetProcAddress(_hOLE32DLL, "CoGetActivationState"); //在WCT等待链中注册COM函数 RegisterWaitChainCOMCallback(CallStateCallback, ActivationStateCallback); } ////////////////////////////////////////////////////////////////////////// void CWCT::ParseThreads(DWORD PID){ _PID = PID; //枚举进程中所有的线程 CToolhelp th(TH32CS_SNAPTHREAD, PID); THREADENTRY32 te = { sizeof(te) }; BOOL fOk = th.ThreadFirst(&te); for (; fOk;fOk=th.ThreadNext(&te)){ //只分析当前进程中的线程 if (te.th32OwnerProcessID == PID){ ParseThread(te.th32ThreadID); } } } ////////////////////////////////////////////////////////////////////////// void CWCT::ParseThread(DWORD TID){ WAITCHAIN_NODE_INFO chain[WCT_MAX_NODE_COUNT]; DWORD dwNodesInChain; BOOL bDeadlock; dwNodesInChain = WCT_MAX_NODE_COUNT; //获取当前线程的等待链 /* 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表示检测到死锁 */ if (!GetThreadWaitChain(_hWCTSession,NULL,WCTP_GETINFO_ALL_FLAGS, TID,&dwNodesInChain,chain,&bDeadlock)){ //调用失败时 _dwNodeCount = 0; OnThread(TID, FALSE, 0);//当dwNodeCount=0表示访问被拒绝 return; //结束 } //开始分析线程TID的等待链 _dwNodeCount = min(dwNodesInChain, WCT_MAX_NODE_COUNT); OnThread(TID, bDeadlock, dwNodesInChain); //对于每个等待链中的节点,调用虚函数 for (DWORD i = 0; i < min(dwNodesInChain, WCT_MAX_NODE_COUNT); i++){ OnChainNodeInfo(TID, i, chain[i]); } } ////////////////////////////////////////////////////////////////////////// DWORD CWCT::GetNodesInChain(){ return (_dwNodeCount); } ////////////////////////////////////////////////////////////////////////// DWORD CWCT::GetPID(){ return (_PID); } ////////////////////////////////////////////////////////////////////////// void CWCT::OnThread(DWORD TID, BOOL bDeadlock, DWORD nodeCount){ //虚函数,留给派生类去执行 } ////////////////////////////////////////////////////////////////////////// void CWCT::OnChainNodeInfo(DWORD rootTID, DWORD currentNode, WAITCHAIN_NODE_INFO nodeInfo){ //虚函数,留给派生类去执行 } ///////////////////////////////文件结束///////////////////////////////////
//ChainParser.h
/************************************************************************* Module:ChainParser.h Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre *************************************************************************/ #pragma once ////////////////////////////////////////////////////////////////////////// #include "../../CommonFiles/WaitChainTraversal.h" #include <tchar.h> #include <strsafe.h> ////////////////////////////////////////////////////////////////////////// class CChainParser :public CWCT { public: CChainParser(HWND hEdit); protected: //每个线程在被分析时都会调用该函数 //注意:当nodeCount=0时,表示该线程不能被分析 virtual void OnThread(DWORD TID, BOOL bDeadlock, DWORD nodeCount); //每个结点 virtual void OnChainNodeInfo(DWORD rootTID, DWORD currentNode, WAITCHAIN_NODE_INFO nodeInfo); private: void AddText(PCTSTR pszFormat, ...); LPCTSTR GetWCTObjectType(WCT_OBJECT_TYPE objectType); LPCTSTR GetWCTObjectStatus(WCT_OBJECT_STATUS objectStatus); void GetLastErrorMessage(LPTSTR szMsg, size_t cchLength); private: HWND _hEdit; }; ////////////////////////////////////////////////////////////////////////// CChainParser::CChainParser(HWND hEdit){ _hEdit = hEdit; } ////////////////////////////////////////////////////////////////////////// void CChainParser::OnThread(DWORD TID, BOOL bDeadlock, DWORD nodeCount){ //检查线程是否可被分析 if (nodeCount == 0){ //返回失败的原因 TCHAR szMsg[1024]; GetLastErrorMessage(szMsg, _countof(szMsg)); AddText(TEXT("...线程%u - 错误\r\n %s\r\n"), TID, szMsg); } else{ AddText(TEXT("...线程%u %s\r\n"), TID, bDeadlock?TEXT("发生死锁"):TEXT("")); } } ////////////////////////////////////////////////////////////////////////// void CChainParser::OnChainNodeInfo(DWORD rootTID, DWORD currentNode, WAITCHAIN_NODE_INFO nodeInfo){ //增加一个分隔线 if (currentNode==0) AddText(TEXT("---------------------------------------------------------------------------\r\n")); //如果该节点表示被阻塞的线程。(其下个元素就是等待的原因) if (nodeInfo.ObjectType == WctThreadType){ //该阻塞线程如果是哪一个进程中的线程 if (GetPID() !=nodeInfo.ThreadObject.ProcessId){ AddText(TEXT(" [%u:%u->%s] "), nodeInfo.ThreadObject.ProcessId, nodeInfo.ThreadObject.ThreadId, GetWCTObjectStatus(nodeInfo.ObjectStatus)); } else{ AddText(TEXT(" [%u->%s] "), nodeInfo.ThreadObject.ThreadId, GetWCTObjectStatus(nodeInfo.ObjectStatus)); } } else{ //其他节点,就是等待的原因,一定是下列原因之一 //如果是内核对象,显示名称(内核中使用的是Unicode字符) if (nodeInfo.LockObject.ObjectName[0]!=L'\0'){ //名字不为空 #ifdef UNICODE AddText(TEXT("%s - %s(%s)\r\n"), GetWCTObjectType(nodeInfo.ObjectType), nodeInfo.LockObject.ObjectName, GetWCTObjectStatus(nodeInfo.ObjectStatus)); #else //将Unicode转为多字节字符 int iCount = WideCharToMultiByte(CP_ACP, 0, nodeInfo.LockObject.ObjectName, (int)_tcslen(nodeInfo.LockObject.ObjectName), NULL,0,NULL,NULL); CHAR* pszName = (CHAR*)HeapAlloc(GetProcessHeap(), 0, iCount*sizeof(CHAR)); WideCharToMultiByte(CP_ACP, 0, nodeInfo.LockObject.ObjectName, (int)_tcslen(nodeInfo.LockObject.ObjectName), pszName, iCount, NULL, NULL); AddText(TEXT("%s - %s(%s)\r\n"), GetWCTObjectType(nodeInfo.ObjectType), pszName, GetWCTObjectStatus(nodeInfo.ObjectStatus)); HeapFree(GetProcessHeap(), 0, pszName); #endif } else{ //名字为空或原因 AddText(TEXT("%s (%s)\r\n"), GetWCTObjectType(nodeInfo.ObjectType), GetWCTObjectStatus(nodeInfo.ObjectStatus)); } } //最后一个节点时 if (GetNodesInChain() == currentNode + 1){ AddText(TEXT("\r\n\r\n")); } } ////////////////////////////////////////////////////////////////////////// LPCTSTR CChainParser::GetWCTObjectType(WCT_OBJECT_TYPE objectType){ switch (objectType) { case WctCriticalSectionType: return (TEXT("等待临界区")); break; case WctSendMessageType: return (TEXT("等待SendMessage")); break; case WctMutexType: return (TEXT("等待互斥量")); break; case WctAlpcType: return (TEXT("Alpc")); break; case WctComType: return (TEXT("等待COM")); break; case WctThreadWaitType: return (TEXT("等待线程结束")); break; case WctProcessWaitType: return (TEXT("等待进程结束")); break; case WctThreadType: return (TEXT("线程")); break; case WctComActivationType: return (TEXT("正在等待激活COM")); break; case WctUnknownType: return (TEXT("未知")); break; default: return (TEXT("???")); break; } } ////////////////////////////////////////////////////////////////////////// LPCTSTR CChainParser::GetWCTObjectStatus(WCT_OBJECT_STATUS objectStatus){ switch (objectStatus) { case WctStatusNoAccess: return TEXT("拒绝访问!"); //线程状态 break; case WctStatusRunning: //线程状态 return TEXT("运行中"); break; case WctStatusBlocked://线程状态 return TEXT("被阻塞"); break; case WctStatusPidOnly://线程状态 return TEXT("PidOnly!"); break; case WctStatusPidOnlyRpcss://线程状态 return TEXT("PidOnlyRpcss!"); break; case WctStatusOwned://分发对象状态 return TEXT("被拥有"); break; case WctStatusNotOwned://分发对象状态 return TEXT("未被拥有"); break; case WctStatusAbandoned://分发对象状态 return TEXT("被遗弃"); break; case WctStatusUnknown://所有对象 return TEXT("未知状态"); break; case WctStatusError://所有对象 return TEXT("错误"); break; case WctStatusMax: return TEXT("?max?"); break; default: return TEXT("???"); break; } } ////////////////////////////////////////////////////////////////////////// void CChainParser::GetLastErrorMessage(LPTSTR szMsg, size_t cchLength){ DWORD dwLastError = GetLastError(); HLOCAL hlocal = NULL; //用于接收错误信息的缓冲区 //使用系统默认的语言来显示错误信息 DWORD systemLocal = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); //获得错误信息的文本描述 BOOL fOk = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, dwLastError, systemLocal, (PTSTR)&hlocal, 0, NULL); if (fOk && (hlocal != NULL)){ _tcscpy_s(szMsg, cchLength, (PCTSTR)LocalLock(hlocal)); LocalFree(hlocal); } else { StringCchPrintf(szMsg, cchLength, TEXT("未知错误!0x%X"), dwLastError); } //设回最后错误 SetLastError(dwLastError); } ////////////////////////////////////////////////////////////////////////// void CChainParser::AddText(PCTSTR pszFormat, ...){ va_list argList; va_start(argList, pszFormat); TCHAR sz[20 * 1024]; Edit_GetText(_hEdit, sz, _countof(sz)); _vstprintf_s(_tcschr(sz, TEXT('\0')), _countof(sz) - _tcslen(sz), pszFormat, argList); Edit_SetText(_hEdit, sz); va_end(argList); } //////////////////////////////////////////////////////////////////////////
//resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 9_LockCop.rc 使用 // #define IDD_LOCKCOP 101 #define IDC_COMBO_PROCESS 1001 #define IDC_BTN_UPDATE 1002 #define IDC_BTN_REFRESH 1003 #define IDC_EDIT_DETAILS 1004 #define IDI_LOCKCOP 1005 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1006 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//LockCop.rc
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_LOCKCOP DIALOGEX 0, 0, 357, 253 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "LockCop" FONT 10, "宋体", 400, 0, 0x1 BEGIN PUSHBUTTON "退出",IDOK,308,229,38,14 LTEXT "进程:",IDC_STATIC,41,13,25,8 COMBOBOX IDC_COMBO_PROCESS,67,11,230,160,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "刷新",IDC_BTN_REFRESH,303,10,38,14 GROUPBOX "锁的详细信息",IDC_STATIC,11,30,338,195 EDITTEXT IDC_EDIT_DETAILS,17,44,328,178,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL ICON IDI_LOCKCOP,IDI_LOCKCOP,10,6,20,20 DEFPUSHBUTTON "更新",IDC_BTN_UPDATE,66,27,38,14 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO BEGIN IDD_LOCKCOP, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 350 TOPMARGIN, 3 BOTTOMMARGIN, 246 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_LOCKCOP ICON "LockCop.ico" #endif // 中文(简体,中国) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
【BadLock程序】产生死锁,用于测试之用
/************************************************************* Module:BadLock.cpp Notics:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre *************************************************************/ #include <windows.h> #include <tchar.h> #include <locale.h> ////////////////////////////////////////////////////////////////////////// DWORD WINAPI RuningThreadHandler(PVOID pvParam) { //设置线程优先级为最低 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); //无限循环 for (;;); return 0; } void RuningThread(){ DWORD dwThreadID; CloseHandle(CreateThread(NULL, 0, RuningThreadHandler, (PVOID)1, 0, &dwThreadID)); _tprintf(_T("运行中的线程:\n")); _tprintf(_T(" 线程ID=%u\n"), dwThreadID); } ////////////////////////////////////////////////////////////////////////// //创建一个永远等待互斥量的线程 DWORD WINAPI LockInfiniteMutexHandler(PVOID pvParam) { HANDLE hMutex = (HANDLE)pvParam; WaitForSingleObject(hMutex, INFINITE); _tprintf(_T("线程%u永远不能运行到这里!"), GetCurrentThreadId()); return 0; } ////////////////////////////////////////////////////////////////////////// //创建一个死循环的线程 void LockInfinite() { //第2个参数为TRUE,说明初始化时该互斥量被主调线程所拥有。 HANDLE hMutex = CreateMutex(NULL, TRUE, TEXT("InfiniteMutex")); DWORD dwThreadID; CloseHandle(CreateThread(NULL, 0, LockInfiniteMutexHandler, (PVOID)hMutex, 0, &dwThreadID)); _tprintf(_T("线程%u正在无限等待互斥量0x%X:\n"),dwThreadID,PtrToUlong(hMutex)); } ////////////////////////////////////////////////////////////////////////// //互等互斥量造成的死锁 CRITICAL_SECTION cs1; CRITICAL_SECTION cs2; DWORD WINAPI LockCriticalSectionHandler(PVOID pvParam) { DWORD dwAction = PtrToLong(pvParam); if (dwAction ==1){ EnterCriticalSection(&cs1); Sleep(1000); EnterCriticalSection(&cs2); } else { EnterCriticalSection(&cs2); Sleep(1000); EnterCriticalSection(&cs1); } return 0; } void LockCriticalSections(){ InitializeCriticalSection(&cs1); InitializeCriticalSection(&cs2); DWORD dwThreadID1; CreateThread(NULL, 0, LockCriticalSectionHandler, (PVOID)1, 0, &dwThreadID1); DWORD dwThreadID2; CreateThread(NULL, 0, LockCriticalSectionHandler, (PVOID)2, 0, &dwThreadID2); _tprintf(_T("锁定临界区:\n")); _tprintf(_T(" 线程1=%u\n"), dwThreadID1); _tprintf(_T(" 线程2=%u\n"), dwThreadID2); _tprintf(_T(" 临界区1=0x%X\n"), PtrToUlong(&cs1)); _tprintf(_T(" 临界区2=0x%X\n"), PtrToUlong(&cs2)); } ////////////////////////////////////////////////////////////////////////// //3个线程互等3个互斥量造成的死锁 HANDLE hMutex1, hMutex2, hMutex3; DWORD WINAPI LockMutexHandler(PVOID pvParam) { DWORD dwAction = PtrToUlong(pvParam); if (dwAction == 1){ WaitForSingleObject(hMutex1, INFINITE); Sleep(1000); WaitForSingleObject(hMutex2, INFINITE); } else if (dwAction == 2){ WaitForSingleObject(hMutex2, INFINITE); Sleep(1000); WaitForSingleObject(hMutex3, INFINITE); } else{ //dwAction==3 WaitForSingleObject(hMutex3, INFINITE); Sleep(1000); WaitForSingleObject(hMutex1, INFINITE); } return 0; } void LockMutex() { hMutex1 = CreateMutex(NULL, FALSE, TEXT("FirstMutex")); hMutex2 = CreateMutex(NULL, FALSE, TEXT("SecondMutex")); hMutex3 = CreateMutex(NULL, FALSE, TEXT("ThirdMutex")); DWORD threadID1; CloseHandle(CreateThread(NULL, 0, LockMutexHandler, (PVOID)1, 0, &threadID1)); DWORD threadID2; CloseHandle(CreateThread(NULL, 0, LockMutexHandler, (PVOID)2, 0, &threadID2)); DWORD threadID3; CloseHandle(CreateThread(NULL, 0, LockMutexHandler, (PVOID)3, 0, &threadID3)); _tprintf(_T("锁定互斥量:\n")); _tprintf(_T(" 线程1=%u\n"), threadID1); _tprintf(_T(" 线程2=%u\n"), threadID2); _tprintf(_T(" 线程3=%u\n"), threadID3); _tprintf(_T(" 互斥量1=0x%X\n"), PtrToUlong(hMutex1)); _tprintf(_T(" 互斥量2=0x%X\n"), PtrToUlong(hMutex2)); _tprintf(_T(" 互斥量3=0x%X\n"), PtrToUlong(hMutex3)); } ////////////////////////////////////////////////////////////////////////// //等待被遗弃的临界区 CRITICAL_SECTION csAbandonned; DWORD WINAPI AbandonnedCriticalSectionHandler(PVOID pvParam) { if (pvParam == NULL){ //第1个线程进入临界区 EnterCriticalSection(&csAbandonned); _tprintf(_T(" 线程%u遗弃了一个临界区\n"),GetCurrentThreadId()); return 0; //没释放临界区直接退出线程,造成“遗弃”现象 } else{ //第2个线程 _tprintf(_T(" 线程%u正在进入一个被遗弃的临界区\n"), GetCurrentThreadId()); EnterCriticalSection(&csAbandonned); _tprintf(_T(" 线程%u正在离开一个被遗弃的临界区\n"), GetCurrentThreadId()); LeaveCriticalSection(&csAbandonned); _tprintf(_T(" 线程%u己经离开一个被遗弃的临界区\n"), GetCurrentThreadId()); return 0; } } void AbandonnedCriticalSection() { InitializeCriticalSection(&csAbandonned); DWORD threadID; HANDLE hThread = CreateThread(NULL, 0, AbandonnedCriticalSectionHandler,NULL,0,&threadID); _tprintf(_T("遗弃临界区:\n")); _tprintf(_T(" 线程=%u\n"), threadID); Sleep(1000); hThread = CreateThread(NULL, 0, AbandonnedCriticalSectionHandler, (PVOID)1, 0, &threadID); } ////////////////////////////////////////////////////////////////////////// //等待进程(记事本)结束 DWORD WINAPI LockProcessHandler(PVOID pvParam) { HANDLE hProcess = (HANDLE)pvParam; WaitForSingleObject(hProcess, INFINITE); CloseHandle(hProcess); return TRUE; } void LockProcess() { STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; TCHAR sz[] = _T("notepad"); CreateProcess(NULL, sz, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); CloseHandle(pi.hThread); DWORD threadID; CloseHandle(CreateThread(NULL, 0, LockProcessHandler, pi.hProcess, 0, &threadID)); _tprintf(_T("锁定进程=%u\n"), threadID); _tprintf(_T(" 记事本=%u\n"), pi.dwProcessId); } ////////////////////////////////////////////////////////////////////////// DWORD WINAPI SimpleThreadHandler(PVOID pvParam) { WaitForSingleObject((HANDLE)pvParam, INFINITE); return 0; } DWORD WINAPI BlockedThreadHandler(PVOID pvParam) { HANDLE hParentThread; DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(),&hParentThread,0,FALSE,DUPLICATE_SAME_ACCESS); HANDLE handles[3]; DWORD threadID1; handles[0] = CreateThread(NULL, 0, SimpleThreadHandler, hParentThread, 0, &threadID1); DWORD threadID2; handles[1] = CreateThread(NULL, 0, SimpleThreadHandler, hParentThread, 0, &threadID2); DWORD threadID3; handles[2] = CreateThread(NULL, 0, SimpleThreadHandler, hParentThread, 0, &threadID3); _tprintf(_T("被线程(%u)阻塞的线程\n"),GetCurrentThreadId()); _tprintf(_T(" 线程1=%u(0x%X)\n"), threadID1,PtrToUlong(handles[0])); _tprintf(_T(" 线程2=%u(0x%X)\n"), threadID2, PtrToUlong(handles[1])); _tprintf(_T(" 线程3=%u(0x%X)\n"), threadID3, PtrToUlong(handles[2])); Sleep(100); //注意WCT不处理WaitForMultipleObjects WaitForMultipleObjects(3, handles, TRUE, INFINITE); //WaitForSingleObject(handles[0],INFINITE); _tprintf(_T("结束锁定线程\n")); return 0; } void LockThreads() { DWORD threadID; CloseHandle( CreateThread(NULL, 0, BlockedThreadHandler, NULL, 0, &threadID)); } ////////////////////////////////////////////////////////////////////////// void TestDeadLock() { _tprintf(_T("测试死锁 进程ID:%u\n"), GetCurrentProcessId()); _tprintf(_T("----------------------------\n")); RuningThread();//无限循环 LockInfinite();//创建一个永远等待互斥量的线程 LockCriticalSections();//互等互斥量造成的死锁 LockMutex(); //3个线程互等3个互斥量造成的死锁 AbandonnedCriticalSection();//等待被遗弃的临界区 LockProcess(); //等待进程(记事本)结束 LockThreads(); } int _tmain() { _tsetlocale(LC_ALL, _T("chs")); TestDeadLock(); MessageBox(NULL, _T("按确定结束程序"), TEXT("提示"), MB_OK); _tsystem(_T("PAUSE")); return 0; }