Raymond Chen 2004年01月28日
简介
这篇文章讨论了在DLL的
DllMain
函数中执行某些操作时可能发生的死锁问题
正文
你的DllMain
函数是在加载器锁内部运行的,这是操作系统允许你在其内部锁之一被持有时执行代码的少数几次机会之一。这意味着你必须格外小心,不要在你的DllMain
中违反锁层次结构;否则,你就在自找死锁。 (你的DLL中确实有锁层次结构,对吧?)
加载器锁是由任何需要访问进程中加载的DLL列表的函数获取的。这包括GetModuleHandle
和GetModuleFileName
等函数。 如果你的DllMain
进入了一个临界区或等待一个同步对象,而那个临界区或同步对象又由一些代码拥有,而这些代码又在等待加载器锁,那么你就刚刚创建了一个死锁:
// 全局变量
CRITICAL_SECTION g_csGlobal;
// 某处的一些代码
EnterCriticalSection(&g_csGlobal);
... GetModuleFileName(MyInstance, ..);
LeaveCriticalSection(&g_csGlobal);
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
LPVOID lpvReserved)
{
switch (fdwReason) {
...
case DLL_THREAD_DETACH:
EnterCriticalSection(&g_csGlobal);
...
}
...
}
现在想象一下,某个线程正在愉快地执行第一段代码,并进入了g_csGlobal
,然后挂起。在此期间,另一个线程退出了。这会进入加载器锁,并在仍然持有加载器锁的情况下发送出DLL_THREAD_DETACH
消息。你接收到DLL_THREAD_DETACH
并尝试进入你的DLL的g_csGlobal
。这会在第一个线程上阻塞,该线程拥有临界区。那个线程随后恢复执行并调用GetModuleFileName
。这个函数需要加载器锁(因为它正在访问加载到进程中的DLL列表),所以它被阻塞了,因为加载器锁已经被别的线程拥有了。
现在你遇到了一个死锁:
- 第一个线程拥有
g_cs
,正在等待加载器锁。 - 加载器锁被第二个线程拥有,正在等待
g_cs
。
我亲眼见过这种问题发生,挺糟心的。
这件事告诉我们:谨慎看待加载器锁。如果你在DllMain
中获取任何锁,请将其包含在你的锁层次结构规则中。