本文共 5638 字,大约阅读时间需要 18 分钟。
出于某种需要,我们有时可能会实现一个如下描述的场景:
简单来说,父子窗口分别所属不同的线程。
需求描述完毕,现在进入实现的阶段。我以一个简单的例子来实现这个场景,其中 X 为一个自定义窗口,Y 为一个按钮。为了使 按钮从属线程 B,那么我们需要在线程 B 中创建它,并实现其消息队列的分发。另外,父窗口在某个时机(比如 WM_CREATE)创建线程 B。最后,父窗口希望能够监视到子窗口创建成功,因此用了一个事件(Event)来实现线程的同步,大致代码如下:
1234567891011121314151617181920212223242526272829303132333435 | // 线程参数结构typedef struct _tagCreateParam { HWND hParent; HANDLE hEvent; HWND hBtn;} CREATEPARAM, *PCREATEPARAM; // 按钮线程DWORD WINAPI BtnThread(PVOID param){ PCREATEPARAM p = (PCREATEPARAM)param; p->hBtn = CreateWindow(WC_BUTTON, _T("Button"), WS_CHILD | WS_VISIBLE, 10, 10, 100, 50, p->hParent, (HMENU)1000, g_hInst, NULL); SetEvent(p->hEvent); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0;} // 父窗口的 WM_CREATE 处理器BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct){ CREATEPARAM cp = { 0 }; cp.hParent = hwnd; cp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); g_hBtnThread = CreateThread(NULL, 0, BtnThread, &cp, 0, NULL); WaitForSingleObject(cp.hEvent, INFINITE); return TRUE;} |
代码看起来很合理。不过当它实际跑起来的时候,你会发现这个进程发生了死锁,没有任何反应。
好,下面我的分析开始。如果你知道死锁的原因,那么就可以飘过了。——当然,如果你只知其然不知其所以然,下面的文字应该还是有些用处的。
首先,我们在调试器中将死锁进程暂停。我们知道,线程 A 肯定是死在了 WaitForSingleObject 上,所以无视之,直接查看 BtnThread 的堆栈,如下图。很可惜,这里没什么有用的信息。于是我们不得不进到内核之中,启动 ,找到我们的死锁进程。
PROCESS 8846b3a0 SessionId: 0 Cid: 19a0 Peb: 7ffd4000 ParentCid: 0a68DirBase: 0ac802e0 ObjectTable: e729e9f0 HandleCount: 48.Image: CreateWindowDeadLock.exe |
接下来查看其详细信息,文本很多,不要被弄晕了。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 | 0: kd> !process 8846b3a0PROCESS 8846b3a0 SessionId: 0 Cid: 19a0 Peb: 7ffd4000 ParentCid: 0a68DirBase: 0ac802e0 ObjectTable: e729e9f0 HandleCount: 48.Image: CreateWindowDeadLock.exeVadRoot 883463d0 Vads 53 Clone 0 Private 153. Modified 5. Locked 0.DeviceMap e2ece800Token e51e7d20ElapsedTime 00:07:34.421UserTime 00:00:00.046KernelTime 00:00:00.828QuotaPoolUsage[PagedPool] 51068QuotaPoolUsage[NonPagedPool] 2120Working Set Sizes (now,min,max) (745, 50, 345) (2980KB, 200KB, 1380KB)PeakWorkingSetSize 749VirtualSize 19 MbPeakVirtualSize 23 MbPageFaultCount 769MemoryPriority BACKGROUNDBasePriority 8CommitCharge 247DebugPort 87e47780 THREAD 8a3d8020 Cid 19a0.1e68 Teb: 7ffdf000 Win32Thread: e4934a30 WAIT: (UserRequest) UserMode Non-Alertable87fda2b8 NotificationEventNot impersonatingDeviceMap e2ece800Owning Process 0 Image:Attached Process 8846b3a0 Image: CreateWindowDeadLock.exeWait Start TickCount 2036366 Ticks: 26911 (0:00:07:00.484)Context Switch Count 305 LargeStackUserTime 00:00:00.031KernelTime 00:00:00.000Win32 Start Address 0x004111efStart Address kernel32!BaseProcessStartThunk (0x7c810705)Stack Init a5d0c740 Current a5d0c3e0 Base a5d0d000 Limit a5d09000 Call a5d0c74cPriority 9 BasePriority 8 PriorityDecrement 0 DecrementCount 16Kernel stack not resident.ChildEBP RetAddra5d0c3f8 80504850 nt!KiSwapContext+0x2f (FPO: [Uses EBP] [0,0,4])a5d0c404 804fc078 nt!KiSwapThread+0x8a (FPO: [0,0,0])a5d0c42c 805c176c nt!KeWaitForSingleObject+0x1c2 (FPO: [5,5,4])a5d0c490 8054263c nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])a5d0c490 7c92e514 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ a5d0c4a4)0012f480 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0]) THREAD 87fac560 Cid 19a0.1844 Teb: 7ffde000 Win32Thread: e726aeb0 WAIT: (WrUserRequest) UserMode Non-Alertable884d66b0 SynchronizationEventNot impersonatingDeviceMap e2ece800Owning Process 0 Image:Attached Process 8846b3a0 Image: CreateWindowDeadLock.exeWait Start TickCount 2038243 Ticks: 25034 (0:00:06:31.156)Context Switch Count 41 NoStackSwap LargeStackUserTime 00:00:00.000KernelTime 00:00:00.000Win32 Start Address 0x00411226Start Address kernel32!BaseThreadStartThunk (0x7c8106f9)Stack Init a5a72000 Current a5a719f0 Base a5a72000 Limit a5a6e000 Call 0Priority 10 BasePriority 8 PriorityDecrement 0 DecrementCount 16ChildEBP RetAddra5a71a08 80504850 nt!KiSwapContext+0x2f (FPO: [Uses EBP] [0,0,4])a5a71a14 804fc078 nt!KiSwapThread+0x8a (FPO: [0,0,0])a5a71a3c bf802f45 nt!KeWaitForSingleObject+0x1c2 (FPO: [5,5,4])a5a71a78 bf840f3c win32k!xxxSleepThread+0x192 (FPO: [3,5,4])a5a71b14 bf8141ba win32k!xxxInterSendMsgEx+0x7f6 (FPO: [Non-Fpo])a5a71b60 bf80ecc1 win32k!xxxSendMessageTimeout+0x11f (FPO: [7,7,0])a5a71b84 bf83e1d0 win32k!xxxSendMessage+0x1b (FPO: [4,0,0]) ; <-- 注意这里a5a71c6c bf834af7 win32k!xxxCreateWindowEx+0xd0d (FPO: [15,49,0])a5a71d20 8054263c win32k!NtUserCreateWindowEx+0x1c1 (FPO: [Non-Fpo])a5a71d20 7c92e514 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ a5a71d64)00abfd98 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0]) |
请注意第 67 行。如无意外,我们的死锁应该是发生在 SendMessage 里面了。查看其调用参数,如下。
0: kd> dd a5a71b84 l6a5a71b84 a5a71c6c bf83e1d0 bbf44570 00000210 ; <-- 注意这里a5a71b94 03e80001 00101100 |
好了,答案已经浮出水面。0×210 这个数值对应的消息是 WM_PARENTNOTIFY。当子窗口创建时,会向其父窗口(窗口 X)发送这个消息并无限等待。但是,窗口 X 所在的线程 A 正在 WaitForSingleObject,无法进行消息的处理,因此造成了两个线程的互锁。
文中提到的测试代码见附件。
转载地址:http://iuosx.baihongyu.com/