在 Windows 7 下如何使用 native c++ 正确创建线程
对于 Windows 7 上的 C++ 程序员来说,创建一个 thread 有以下五种选择。注意,本文最后一个提到的 AfxBeginThread 是用于创建一个 UI 线程,可等同于用于创建 worker thread 的倒数第二个 AfxBeginThread,这里不讨论它。
HANDLE WINAPI CreateThread( __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in SIZE_T dwStackSize, __in LPTHREAD_START_ROUTINE lpStartAddress, __in_opt LPVOID lpParameter, __in DWORD dwCreationFlags, __out_opt LPDWORD lpThreadId ); uintptr_t _beginthread( void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist ); uintptr_t _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr ); CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
看到这四个函数,一个很自然的问题就是 - 那我到底该用哪一个呢?答案是,搞清楚它们之间的区别后,该用哪个取决于具体应用场景。这里浅谈一下它们之间的区别。
CreateThread 是 Windows 的API 函数,提供 OS 级别的创建 thread 的操作,且仅限于 worker thread。主函数中应当避免使用 MFC 和 CRT 的函数。这是因为在它的层面不会做下面两件事:
- CRT 需要对多线程进行记录和初始化,以保证 C 函数库工作正常,比如一个典型的例子是 strtok。
- MFC 也需要对多线程进行记录和初始化,以保证 MFC 函数库工作正常。
_beginthread 和 _beginthreadex 是由 multithreaded 版本的 CRT 提供的,header 是 afxwin.h。创建 thread,其实也是通过 Windows API CreateThread 来实现的。结束 thread,可在主函数(准确的说,是线程 run 到的所有代码,下同)内通过显式调用 _endthread( ) or _endthreadex(unsigned retval) 。注意,当线程从主函数返回后,二者自动会被调用。显式调用二者,可以帮助确保为该 thread 分配的 resource 被恰当恢复,最典型的比如 _tiddata 内存块(/* Structure for each thread's data */),阅读代码便知,_beginthreadex/_beginthreadex 负责分配 _tiddata 内存块,_endthread/_endthreadex 负责释放 _tiddata 内存块。另外还有一点要特别注意,_endthread( ) 会自动 close thread handle, _endthreadex 不会。因此,如果是用 _beginthreadex 创建的 thread,一定要通过调用 CloseHandle API 来手工 close thread handle。
再举个例子,如果使用 CreateThread 创建 thread,但 thread 的主函数却调用了需要读取 _tiddata 的 CRT 函数,CRT 就会为该线程分配一个 _tiddata 内存块。问题来了,CreateThread 创建的 thread,结束时并没有调用 _endthreadex,内存泄漏就这样悄无声息的发生了。我想这就是为什么 MSDN 上会有下面这段话的原因:
A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multithreaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.[1]
AfxBeginThread 是由 MFC 提供,header 是 afxwin.h。要 end thread,可在主函数内调 AfxEndThread 或直接从 thread 主函数返回。注意不要在一个 MFC 程序中使用 _beginthreadex 或者 CreateThread 来创建 thread。通过阅读该函数的源码不难理解原因。它首先创建了相应的 CWinThread 对象,然后调用 CWinThread::CreateThread 来创建 thread。而 CWinThread::CreateThread 函数一开始就做了很多 MFC 特有的 thread 初始化的操作,比如创建了两个 event 内核对象;然后通过 _beginthreadex 创建 thread。如果在 MFC 程序中,用 MFC 来创建 thread,又用 CRT 或者 Windows API 对 thread,就会出现控制混乱的局面,从而导致一些无法预料的错误。
总结一下:对于 thread 的主函数,如果会调用 CRT 的函数,则必须确保会成对使用 _beginthreadex/_endthreadex; 如果会调用 MFC 的函数,则必须确保会成对使用 AfxBeginThread/ AfxEndThread;如果都没有,才可以使用 CreateThread/ExitThread。一个大致的从顶层到底层的逻辑包含关系是,AfxBeginThread 包含 _beginthreadex; _beginthread 是 _beginthreadex 的功能子集(详细区别在 threadex.c 文件中),但二者都是 CRT 层面;_beginthread 和 _beginthreadex 又包含 CreateThread。
References:
[1] http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453%28v=vs.85%29.aspx , CreateThread reference
[2] http://www.cnblogs.com/yuaqua/archive/2011/11/18/2253492.html , CreateThread, AfxBeginThread, _beginthread, _beginthreadex
[3] http://msdn.microsoft.com/en-us/library/s3w9x78e%28v=vs.100%29.aspx , AfxBeginThread reference.
[4] http://msdn.microsoft.com/en-us/library/984x0h58%28v=vs.80%29.aspx , __cdecl, __stdcall, __fastcall
[5] http://msdn.microsoft.com/en-us/library/kdzttdcb%28v=vs.100%29.aspx , _beginthread vs _beginthreadex
<EOF>