DllMain和多线程死锁

发布时间:2011-08-29 共3页

  // 清除资源

  ::CloseHandle(g_thread_handle);

  g_thread_id = 0 ;

  g_thread_handle = NULL ;

  ::CloseHandle(g_hEvent);

  g_hEvent=NULL;

  }

  break;

  case DLL_PROCESS_DETACH:

  // DLL正在从进程地址空间中卸载

  break;

  }

  return TRUE;

  }

  //----------------------end   ------------

  如果对这样的程序进行调试,通过Call Stack窗口可以看到该程序正在等待DllMain内部的线程处理,而Output窗口中也没有打印出“---- operations.---- ”语句。可见线程函数InSideDll_ThreadProc根本就没有得到运行的机会。

  结合DllMain的顺序调用规则,答案就很简单了。在程序运行过程中,第一个线程对LoadLibrary的调用引起操作系统获取进程互斥对象并以DLL_PROCESS_ATTACH值调用该DLL的DllMain。该DLL的DllMain函数产生第二个线程。无论何时当进程产生一个新线程时,操作系统将获取进程互斥对象,以便于它可以为DLL_THREAD_ATTACH值调用每个加载的DLL的DllMain函数。在这个特定的程序中,第二个线程阻塞,因为第一个线程还保持着进程互斥对象。不幸的是,第一个线程然后调用WaitForSingleObject确认第二个线程能够正确地完成一些操作。因为第二个线程被阻塞在进程互斥对象上,这个进程互斥对象还被第一个线程所持有,而第一个线程要等待第二个线程从而也被阻塞,结果就导致了死锁。如下图所示。

  另外,DisableThreadLibraryCalls函数并不能解除这种死锁,相关原因在《Windows核心编程》一书中有更详尽的描述,这里就不再赘述了。

  2.2、卸载DLL时内部线程为什么没有完全退出

  估计很多人都知道装载DLL过程中的多线程死锁是因为DllMain的顺序调用规则,但是很少人了解卸载DLL过程中的多线程死锁也是由于同样的原因。例如,如果一个DLL的DllMain的代码写成下面的形式,且进程中有至少一个DLL的DllMain没有调用DisableThreadLibraryCalls函数的话,那么卸载该DLL过程中就会因为 DllMain的顺序操作特性带来DLL内部线程没有完全退出的错误。

  //----------------------start   ------------

  HANDLE       g_thread_handle =NULL;       // 该DLL内部线程的句柄

  DWORD       g_thread_id =0;       // 该DLL内部线程的ID

  HANDLE g_hEvent=NULL;// 应答事件的句柄

  DWORD WINAPI InSideDll_ThreadProc( LPVOID p )

  {

  while(1){

  // 收到通知就退出线程函数

  DWORD ret = ::WaitForSingleObject( g_hEvent, INFINITE );

  if(WAIT_TIMEOUT = =ret|| WAIT_OBJECT_0 = =ret) break;

  }

  return true ;

  }

  BOOL APIENTRY DllMain( HANDLE hModule,

  DWORD   ul_reason_for_call,

  LPVOID lpReserved

  )

  {

  switch (ul_reason_for_call)

  {

  case DLL_PROCESS_ATTACH:

  //线程正在映射到进程地址空间中

  {

  // 创建DLL内的线程使用的事件对象

  g_hEvent = ::CreateEvent( NULL, FALSE, FALSE, _T("hello11" ));

百分百考试网 考试宝典

立即免费试用