今天解决了一个关于 mutex 的 bug.
程序每次登录成功就会创建两个线程. sign out 的时候可能会强制 Terminate 这两个线程.
当程序出现问题时用 Process Explorer 观察各个线程. 发现其中一个线程一直在等一个对象,
打开 windbg, 查看 WaitForSingleObject() 的参数. 找到了这个对象的句柄 6d0. 在 windbg 中执行:
0:001>!handle 6d0 f
Handle 6d0
Type Mutant
Attributes 0
GrantedAccess 0x1f0001:
Delete,ReadControl,WriteDac,WriteOwner,Synch
QueryState
HandleCount 4
PointerCount 8
Name none
Object Specific Information
Mutex is Owned
知道了这是一个 mutex. 我用 windbg 也不太熟. google 了一下 windbg mutex. 有所收获.
用这个帖子里的方法: http://blogs.thinktecture.com/ingo/archive/2006/08/05/414674.aspx
可以找到是那个线程拥有了这个 mutex
运行 kd, 发现是恰好是另一个线程获得了这个 mutex. 查看代码, 发现在线程中有这样的代码:
...
if (WaitForSingleObject(hmutex, 100) != WAIT_OBJECT_0) return FALSE;
return TRUE;
如果返回 TRUE, 则认为等到这个 mutex, 操作完成之后, 会调用 ReleaseMutex() 释放这个 mutex. 如果返回 FALSE, 则不会调用 ReleaseMutex().
问题就出在这里. 因为线程可能被强制结束. 那么就有可能没有释放 mutex. 这种情况下, MSDN 的描述是:
If a thread terminates without releasing its ownership of a mutex object, the mutex object is considered to be abandoned. A waiting thread can acquire ownership of an abandoned mutex object, but the wait function will return WAIT_ABANDONED to indicate that the mutex object is abandoned.
下一个线程在等这个"被抛弃"的 mutex 时. WaitForSingleObject() 的返回值是 WAIT_ABANDONED, 而不是 WAIT_OBJECT_0. 而且, 这种情况下, 线程的确获得了 mutex 的控制权. 但上面的代码没有考虑这种情况. 还是返回 FALSE, 导致后继的 ReleaseMutex() 没有调用. 因此导致另一个线程被死锁.
几点思考:
1. 一定不要强制结束线程. 反复强调这一点. 但程序员们还是照用不误.
2. 调用 API 的时候对于返回值的处理要很小心.
PS: 关于 Process Explorer 查看调用栈. 需要几个前提:
. 安装最新的 Process Explorer
. 安装最新的 windbg
. 设置好 _NT_SYMBOL_PATH, 比如:SRV*e:/symbols*http://msdl.microsoft.com/download/symbols
. 使用命令 symchk /r %windir% 将符号下载到本地. 这个比较费时, 可能要下载超过 1G 的符号文件
. 在 Process Explorer 菜单 Options/Configure Symbols 里设置正确的 Dbghelp.dll 路径以及 Symbols Path (默认是 _NT_SYMBOL_PATH 的值)
这些做好之后, 就可以在 Process Explorer 双击一个进程, 切换到 Threads 页. 等初始化完成之后, 双击一个线程, 就可以看到调用栈了.