最近在看梁总的《编程高手箴言》,其中 win32 线程一段的叙述,解决了我一直以来的一个困惑。记之:
很多参考书上,都说不要用 CreateThread 创建线程、并用 CloseHandle 来关闭这个线程,因为这样会导致内存泄露,而应该用 _beginthread 来创建线程,_endthread 来销毁线程。其实,真正的原因并非如此。
CreateThread 后,线程终止运行后,线程对象仍然在系统中,必须通过 CloseHandle 函数来关闭该线程对象。为什么会引起内存泄露呢?因为当线程的函数用到了C的标准库的时候,很容易导致冲突,所以在创建VC的工程时,系统提示是用单线程还是用多线程的库,因为在C的内部有很多的全局变量。例如 errno。
因为在C的库中有全局变量,如果程序中使用了标准的C的库时,很容易导致运行不正常。所以,微软和Borland都对C的库进行了一些改进。但是这个改进的一个条件就是,如果一个线程已经开始创建,就应该创建一个结构来包含这些全局变量,接着把这些全局变量放入线程的上下文中。从而全局变量就会依赖于这个线程,不会引起冲突。
这样做就会有一个问题,什么时候这个线程开始创建呢?标准的C库是不知道的,而 CreateThread 是操作系统的API。所以需要使用 _beginthread/_endthread 来创建/结束线程,让标准C库为线程化做些准备工作。
当用 _beginthread 来创建,而用 CloseHandle 来关闭线程时,标准C库复制的全局变量就不会被释放,这才是前面说的内存泄露的原因。
另一方面,如果用 CreateThread/CloseHandle 来创建/结束线程,则不要使用标准C库的任何函数。还有一个需要注意的,就是在线程执行完之前,不要使用 CloseHandle 来结束线程,否则也会有异常。我们一般通过 WaitForSingleObject/WaitForMultipleObjects 来等待线程结束。
2008-10-19 多谢 amtf 同学提醒
上面说到 "CloseHandle 来结束线程"有误。主线程强制关闭一个子线程,应该先 TerminateThread 然后 CloseHandle。根据 MSDN 所说:"Closing a thread handle does not terminate the associated thread or remove the thread object. To remove a thread object, you must terminate the thread, then close all handles to the thread."
按《核心编程》中所言,CreateThread 创建的 thread_kernel_object.refcount = 2,一个是系统对其的 ref,一个是 thread 本身对其的 ref。当 ExitThread 后,ref - 1,然后 CloseHandle,ref - 1,此时,kernel object 被释放。
《核心》中还坦言,尽量不要使用 _beginthread/_endthread 而是换用对应的 -ex 版本,因此 _endthread 会同时调用 ExitThread/CloseHandle,因此会有下面的问题:
DWORD dwExitCode;
HANDLE hThread = _beginthread(...);
GetExitCodeThread(hThread, &dwExitCode);
CloseHandle(hThread);
如果在 GetExitCodeThread 之前,线程已经执行结束,则 hThread 就已经失效了,因为 _endthread 里面调用了CloseHandle。而 _endthreadex 只会 ExitThread,并不 CloseHandle。
评论