为什么要用_beginthreadex()替代CreateThread()

时间:2024-03-07 22:21:54

开发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