为什么要用_beginthreadex()替代CreateThread()
开发C/C++多线程程序时,要使用C运行库的_beginthreadex()而不是windows API的CreateThread()。
一、 _beginthreadex()
_beginthreadex()是多线程版本C运行库提供的函数。C运行库最初设计的时候还没有多线程的使用,所有很多变量如errno等都是全局共享的。多线程开发时,微软又实现了多线程版本的C运行库。会为每个线程分配一个_tiddata的结构体,用于放置单线程版本C运行库会使用到的数据,避免多线程使用C运行库时的数据冲突。_tiddata定义在Microsoft Visual Studio 9.0\VC\crt\src\mtdll.h中。
摘取部分列于下:
struct _tiddata { unsigned long _tid; /* thread ID */ uintptr_t _thandle; /* thread handle */ int _terrno; /* errno value */ unsigned long _tdoserrno; /* _doserrno value */ unsigned int _fpds; /* Floating Point data segment */ unsigned long _holdrand; /* rand() seed value */ char * _token; /* ptr to strtok() token */ wchar_t * _wtoken; /* ptr to wcstok() token */ unsigned char * _mtoken; /* ptr to _mbstok() token */ /* following pointers get malloc'd at runtime */ char * _errmsg; /* ptr to strerror()/_strerror() buff */ wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */ char * _namebuf0; /* ptr to tmpnam() buffer */ wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ char * _namebuf1; /* ptr to tmpfile() buffer */ wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ char * _asctimebuf; /* ptr to asctime() buffer */ wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ void * _gmtimebuf; /* ptr to gmtime() structure */ char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */ unsigned char _con_ch_buf[MB_LEN_MAX]; /* ptr to putch() buffer */ unsigned short _ch_buf_used; /* if the _con_ch_buf is used */ // 此处略去一些代码 };
_beginthreadex()在也是调用CreateThread()创建线程的,在调用CreateThread()之前会分配一个_tiddata结构用于新建线程使用。_beginthreadex()实现代码在Microsoft Visual Studio 9.0\VC\crt\src\threadex.c中。
其代码摘略如下:
_MCRTIMP uintptr_t __cdecl _beginthreadex ( void *security, unsigned stacksize, unsigned (__CLR_OR_STD_CALL * initialcode) (void *), void * argument, unsigned createflag, unsigned *thrdaddr ) { _ptiddata ptd; /* pointer to per-thread data */ // 此处略去一些代码 /* * Allocate and initialize a per-thread data structure for the to- * be-created thread. */ if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL ) goto error_return; /* * Initialize the per-thread data */ _initptd(ptd, _getptd()->ptlocinfo); ptd->_initaddr = (void *) initialcode; ptd->_initarg = argument; ptd->_thandle = (uintptr_t)(-1); // 此处略去一些代码 /* * Create the new thread using the parameters supplied by the caller. */ if ( (thdl = (uintptr_t) CreateThread( (LPSECURITY_ATTRIBUTES)security, stacksize, _threadstartex, (LPVOID)ptd, createflag, (LPDWORD)thrdaddr)) == (uintptr_t)0 ) { err = GetLastError(); goto error_return; } /* * Good return */ return(thdl); // 此处略去一些代码 } static unsigned long WINAPI _threadstartex ( void * ptd ) { _ptiddata _ptd; /* pointer to per-thread data */ // 此处略去一些设置tiddata的代码 _callthreadstartex(); /* * Never executed! */ return(0L); } static void _callthreadstartex(void) { _ptiddata ptd; /* pointer to thread's _tiddata struct */ /* must always exist at this point */ ptd = _getptd(); /* * Guard call to user code with a _try - _except statement to * implement runtime errors and signal support */ __try { _endthreadex ( ( (unsigned (__CLR_OR_STD_CALL *)(void *))(((_ptiddata)ptd)->_initaddr) ) ( ((_ptiddata)ptd)->_initarg ) ) ; } __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) { /* * Should never reach here */ _exit( GetExceptionCode() ); } /* end of _try - _except */ } void __cdecl _endthreadex ( unsigned retcode ) { _ptiddata ptd; /* pointer to thread's _tiddata struct */ // 此处略去一些代码 ptd = _getptd_noexit(); if (ptd) { /* * Free up the _tiddata structure & its subordinate buffers * _freeptd() will also clear the value for this thread * of the FLS variable __flsindex. */ _freeptd(ptd); } /* * Terminate the thread */ ExitThread(retcode); }
_beginthreadex()调用后发生的操作如下:
1、_beginthreadex新建一个tiddata,将线程函数和参数保存到里面,然后调用windows API的CreateThread(),其线程函数为_threadstartex(),参数为tiddata
2、_threadstartex()设置完tiddata后,调用_callthreadstartex()
3、_callthreadstartex()新建了一个SHE异常帧,然后执行_beginthreadex()参数中的线程函数,执行完毕后,调用_endthreadex()
4、_endthreadex()会获取tiddata,然后将其释放,最后调用ExitThread()退出线程。
注意,_beginthreadex()在正常运行结束后,会自动调用_endthreadex()。
二、CreateThread()
CreateThread()是windows提供的API用来创建线程。_beginthreadex()也是需要调用该API来创建线程的。
如果是用CreateThread(),一般是使用CloseHandle()关闭句柄。如果在线程中使用了诸如strtok()等函数(_tiddata结构成员的注释标注了这些函数),C运行库会尝试读取该线程的tiddata,如果没有,则会分配一个。这样在使用CloseHandle()关闭句柄时,tiddata未被释放,造成内存泄露。使用CreatThread()创建,调用_endthreadex()关闭,又显得不匹配。所以还是建议使用_beginthreadex()。
strtok()的实现在Microsoft Visual Studio 9.0\VC\crt\src\strtok.c中。
代码摘略如下:
char * __cdecl strtok ( char * string, const char * control ) { unsigned char *str; const unsigned char *ctrl = control; unsigned char map[32]; int count; // 此处略去一些代码 _ptiddata ptd = _getptd(); // 此处略去实现代码 } _ptiddata __cdecl _getptd ( void ) { _ptiddata ptd = _getptd_noexit(); if (!ptd) { _amsg_exit(_RT_THREAD); /* write message and die */ } return ptd; } _ptiddata __cdecl _getptd_noexit ( void ) { _ptiddata ptd; DWORD TL_LastError; TL_LastError = GetLastError(); #ifdef _M_IX86 /* * Initialize FlsGetValue function pointer in TLS by calling __set_flsgetvalue() */ if ( (ptd = (__set_flsgetvalue())(__flsindex)) == NULL ) { #else /* _M_IX86 */ if ( (ptd = FLS_GETVALUE(__flsindex)) == NULL ) { #endif /* _M_IX86 */ /* * no per-thread data structure for this thread. try to create * one. */ #ifdef _DEBUG extern void * __cdecl _calloc_dbg_impl(size_t, size_t, int, const char *, int, int *); if ((ptd = _calloc_dbg_impl(1, sizeof(struct _tiddata), _CRT_BLOCK, __FILE__, __LINE__, NULL)) != NULL) { #else /* _DEBUG */ if ((ptd = _calloc_crt(1, sizeof(struct _tiddata))) != NULL) { #endif /* _DEBUG */ // 此处略去一些代码 }
strtok()会去调用_getptd(),_getptd()调用_getptd_noexit(),这两个函数实现都在Microsoft Visual Studio 9.0\VC\crt\src\tidtable.c中。当线程不存在tiddata时,会分配内存新建一个tiddata。
小结:
在开发C/C++多线程程序时,尽量使用_beginthreadex()而不要使用CreateThread()。
如果使用CreateThread(),则需要确保不在线程函数中使用到C运行库诸如strtok()等函数(可使用替代的windows API),否则会造成tiddata结构的内存泄露。
参考资料:
1、《windows via c/c++》 Chapter 6: Thread Basics
这是最好的时代,这是最坏的时代;这是智慧的年代,这是愚蠢的年代;这是信仰的时期,这是怀疑的时期;我们的前途拥有一切,我们的前途一无所有。