CreateThread和_beginthreadex的区别

网址:

http://www.soft-bin.com/html/2010/08/03/createthread_and_beginthreadex.html

在使用VS创建一个工程时,我们可以选择使用的run-time library,在run-time libary的下拉菜单中,有 Single-Threaded, Multithreaded, MultiThreaded DLL等好几种选项,不同的选项会为我们链接不同的C++ rum-time库。

之所以run-time库会有单线程和多线程的区别,是因为标准C运行库在最初设计时,并没有考虑到其将被运用于多线程的应用程序,这将会引起一些问题。

例如对于C运行期的全局变量errno,我们在多线程环境下,不能保证在设置errno和获取errno之间,没有其他线程对这个全局变量进行了设置,这就会产生一定的问题。而要解决这个问题,我们可以使用线程专属全局变量 ,这个概念我将在以后讲到,但在这个地方,C++ run-time使用的方式是在创建新线程时,为每一个线程分配一个数据块,来保存这些全局的信息。

在Windows下创建线程调用的是Windows的API函数CreateThread,但CreateThread只是一个与操作系统相关的函数,负责在操作系统内部创建线程内核对象,为线程分配栈空间。为线程的运行准备适宜的条件,并不是操作系统应该考虑的事情。比如说,C++ run-time需要在创建线程时,创建一个线程专属的errno变量,但可能其他的运行环境又没有这一项要求,我们不能强制要求Windows操作系统去迎合C++ run-time的需求。

CreateThread并不能正确地初始化C++ run-time对于多线程环境的要求,因此C++ run-time提供了自己的线程创建函数 _beginthreadex,其函数原型如下:

unsigned long _beginthreadex(
    void* security,
    unsigned stack_size,
    unsigned (*start_address)(void*),
    void* arglist,
    unsigned initflag,
    unsigned* thrdaddr);

这个函数的参数和CreateThread的参数基本一致,不同的是参数类型不同。这是因为C++ run-time并不从属于操作系统,不应当对操作系统的数据类型有任何依赖。因此我们可以看到 _beginthreadex 函数的参数完全是基本的C++数据类型。

_beginthreadex 函数将会创建线程专属的数据块,并对其进行初始化,而后调用操作系统的线程创建函数以创建一个线程,其伪码如下:

 

unsigned long _beginthreadex(
    void* security,
    unsigned stack_size,
    unsigned (*start_address)(void*),
    void* arglist,
    unsigned initflag,
    unsigned* thrdaddr)
{
    _ptiddata ptd;  // 指向线程专属数据块的指针,这个结构不作详细介绍,只需知道其中包含线程专属全局变量
    unsigned long thdl;   // 线程句柄
 
    // 申请线程专属数据块
    ptd = _calloc_crt(1, sizeof(struct tiddata));
    initptd(ptd);   // 初始化
 
    // 保存一些需要的信息
    ptd->_initaddr = (void*)pfnStartAddr;    // 保存线程函数地址
    ptd->_initarg = pvParam;   // 保存线程函数的参数
 
     // 创建线程
     thdl = (unsigned long) CreateThread(pas, cbStack, _threadstartex, (PVOID)ptd, fdwCreate, pdwThreadID);
 
     if (thdl == NULL)
     {
           _free_crt(ptd);
           return 0;
     }
      return thdl;
}

我们可以看到,_beginthreadex首先创建了一个线程专属数据块,这个数据块中自然就保存了errno之类的线程专属数据,至于如何获取和设置这些数据,稍后我会提到。 创建线程仍然是调用的Windows API函数CreateThread,因为线程的创建与操作系统息息相关,我们唯有通过其API函数CreateThread才可以创建之。

_beginthreadex 将线程的入口函数变更为了 _threadstartex,我们可以看其伪码:

static unsigned long WINAPI _threadstartex(void* ptd)
{
    // 保存ptd,我们在其他地方会再获取ptd
    TlsSetValue(__tlsidnex, ptd); 
 
    ((_ptiddata) ptd)->_tid = GetCurrentThreadId();
 
    __try
    {
        unsigned ret = ptd->_initaddr(ptd->_initarg);
        _endthreadex(ret);
    }
    __except()
    {
        _exit(GetExceptionCode());
    }
    return 0;
}

我们可以看到 _threadstartex 仍然是调用了 _beginthreadex 函数中最初传入的线程函数,不同的是,在调用线程函数之前,我们使用了异常捕获,防止线程函数崩溃导致程序崩溃。在线程函数执行完毕后,_threadstartex会紧接着调用 _endthreadex函数,这个函数的作用有两点:

1. 释放线程专属数据块,避免内存泄漏
2. 调用ExitThread退出线程,将退出码写入到线程内核对象中

至此我们可以知道,CreateThread和_beginthreadex两个函数的区别在于:

CreateThread是操作系统提供的创建线程的API,_beginthreadex是C++ run-time提供的用于对线程进行初始化,创建线程转悠数据块的函数。其内部创建线程的操作,仍然通过CreateThread来实现。

我们在编写C++程序创建线程时,应当使用_beginthreadex,而绝不要使用CreateThread,否则会引起错误。

在我们使用_beginthreadex函数时,必须选择run-time链接选项为多线程库,否则会出现找不到_beginthreadex的错误。

我们之前提到过errno这个全局变量,在run-time为多线程库的时候,它实际上是一个宏:

#if defined(_MT) || defined(_DLL)
    extern int * __cdecl _errno(void);
    #define errno (*_errno())
#else
    extern int errno;
#endif

我们在选择多线程库后,使用errno时,实际上是调用了多线程库中的 _errno()函数,这个函数会将线程专属数据块中的errno值获取出来。

可以注意到,_errno() 实际上是取得了线程专属数据块中errno的地址,因此我们也可以对errno进行设置,其行为就如同直接使用一个int类型变量一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值