网址:
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( |
unsigned (*start_address)( void *), |
这个函数的参数和CreateThread的参数基本一致,不同的是参数类型不同。这是因为C++ run-time并不从属于操作系统,不应当对操作系统的数据类型有任何依赖。因此我们可以看到 _beginthreadex 函数的参数完全是基本的C++数据类型。
_beginthreadex 函数将会创建线程专属的数据块,并对其进行初始化,而后调用操作系统的线程创建函数以创建一个线程,其伪码如下:
unsigned long _beginthreadex( |
unsigned (*start_address)( void *), |
ptd = _calloc_crt(1, sizeof ( struct tiddata)); |
ptd->_initaddr = ( void *)pfnStartAddr; |
thdl = (unsigned long ) CreateThread(pas, cbStack, _threadstartex, ( PVOID )ptd, fdwCreate, pdwThreadID); |
我们可以看到,_beginthreadex首先创建了一个线程专属数据块,这个数据块中自然就保存了errno之类的线程专属数据,至于如何获取和设置这些数据,稍后我会提到。 创建线程仍然是调用的Windows API函数CreateThread,因为线程的创建与操作系统息息相关,我们唯有通过其API函数CreateThread才可以创建之。
_beginthreadex 将线程的入口函数变更为了 _threadstartex,我们可以看其伪码:
static unsigned long WINAPI _threadstartex( void * ptd) |
TlsSetValue(__tlsidnex, ptd); |
((_ptiddata) ptd)->_tid = GetCurrentThreadId(); |
unsigned ret = ptd->_initaddr(ptd->_initarg); |
_exit(GetExceptionCode()); |
我们可以看到 _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()) |
我们在选择多线程库后,使用errno时,实际上是调用了多线程库中的 _errno()函数,这个函数会将线程专属数据块中的errno值获取出来。
可以注意到,_errno() 实际上是取得了线程专属数据块中errno的地址,因此我们也可以对errno进行设置,其行为就如同直接使用一个int类型变量一样。