為什么要用_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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM