博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个跨线程创建窗口的死锁案例
阅读量:5897 次
发布时间:2019-06-19

本文共 5638 字,大约阅读时间需要 18 分钟。

出于某种需要,我们有时可能会实现一个如下描述的场景:

  1. 在线程 A 中,创建一个窗口,称为窗口 X。
  2. 窗口 X 创建后,创建一个子窗口,称为窗口 Y,并且 Y 所属一个新线程,称为线程 B。

简单来说,父子窗口分别所属不同的线程。

需求描述完毕,现在进入实现的阶段。我以一个简单的例子来实现这个场景,其中 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/

你可能感兴趣的文章
在Hadoop-1.2.1中跑著名的wordcount例程
查看>>
css3 -webkit-flex 布局
查看>>
大数据Benchmark
查看>>
windows server2008多用户远程登陆设置方法
查看>>
sencha touch巧妙使用请求超时提升用户体验
查看>>
15. 3Sum
查看>>
ArrayList源码解析
查看>>
基于SpringMVC、Maven以及Mybatis的环境搭建
查看>>
可见面判别算法---区域细分算法
查看>>
清理恢复文本框的默认值
查看>>
ViewPager Banner(广告墙)
查看>>
Spring Cloud 入门教程(二): 服务消费者(rest+ribbon)(Greenwich.RELEASE)
查看>>
iOS开发20:Navigation Bar的简单设置
查看>>
iOS开发24:使用SQLite3存储和读取数据
查看>>
Cocos2dx 2.0x Touch事件
查看>>
Yii2 Unable to verify your data submission 错误-CSRF
查看>>
angularjs-paste-upload
查看>>
解除 Linux 系统的最大进程数和最大文件打开数限制
查看>>
使用优盘或者移动硬盘安装Ubuntu
查看>>
RXjs相关
查看>>