開發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