在多线程环境中存在问题 的 C/C++ 运行期库变量 和函数包括 errno 、 _doserrno 、 strtok 、 _wcstok 、 strerror 、 _strerror 、 tmpnam 、 tmpfile 、 asctime 、 _wasctime 、 gmtime 、 _ecvt 和 _fcvt 等。
所以如果使用上面的变量或函数的话,若要创建一个新线程,绝对不要 调用操作系统的 CreateThread 函数,必须调 C/C++ 运行期库函数 _beginthreadex :
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
注意, _beginthreadex 函数只存在于 C/C + + 运行期库的 多线程版本中 。
_ beginthreadex 的一些要点:
* 每个线程均 获得由 C/C++ 运行期库的 堆栈分配的自己的 tiddata 内存结构 。( tiddata 结构位于 Mtdll.h 文件中的 Visual C++ 源代码中)。
* 传递给 _beginthreadex 的线程函数的地址 保存在 tiddata 内存块中。传递给该函数的参数也保存在该数据块中。
* _beginthreadex 确实从内部调用 CreateThread ,因为这是操作系统了解如何创建新线程的唯一方法 。
* 当调用 CreateThread 时,它被告知通过调用 _threadstartex 而不是 pfnStartAddr 来启动执行新线程。传递给线程函数的参数是 tiddata 结构而不 pvParam 的地址。
* 如果一切顺利,就会像 CreateThread 那样返回线程句柄。如果任何操作失败了,便返回
NULL 。
_ threadstartex 的一些重点:
* 新线程开始从 BasethreadStart 函数(在 kernel32.dll 文件中)执行,然后转移到 _threads tartex 。
* 到达该新线程的 tiddata 块的地址作为其唯一参数被传递给 _threadstartex 。
* TlsSetValue 是个操作系统函数,负责将一个值与调用线程联系起来。 _threadstartex 函数 tiddata 块与线程联系起来。
* 一个 SEH 帧 被放置 在需要的线程函数周围。这个帧 负责处理与运行期库相关 的许多事情—例如,运行期错误(比如放过了没有抓住的 C++ 异常条件)和 C/C++ 运行期库的 s ignal 函数。这是特别重要的。如果用 CreateThread 函数来创建线程,然后调用 C / C + + 运行期库的 signal 函数,那么该函数就不能正确地运行。
* 调用必要的线程函数,传递必要的参数。记住,函数和参数的地址由 _beginthreadex 保存在 tiddata 块中。
* 必要的线程函数返回值被认为是线程的退出代码。注意, _threadstartex 并不只是返回到 BaseThreadStart 。如果它准备这样做,那么线程就终止运行,它的退出代码将被正确地设置,但是线程的 tiddata 内存块不会被撤消。这将导致应用程序中出现一个漏洞。若要防止这个漏洞,可以调用另一个 C/C++ 运行期库函数 _endthreadex , 并传递退出代码。
_endthreadex 的一些要点:
* C 运行期库的 _getptd 函数内部调用操作系统的 TlsGetValue 函数,该函数负责检索调用线程的 tiddata 内存块的地址。
* 然后该数据块被释放,而操作系统的 ExitThread 函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。
ExitThread 函数将撤消调用函数,并且不允许它从当前执行的函数返回。由于该函数不能将任何 C ++ 对象撤消。避免调用 ExitThread 的另一个原因是,它会使得线程的 tiddata 内存块无法 释放, 如果真的想要强制撤消线程,可以让它调用 _endthreadex (而不是调用 ExitThread )以便释放线程的 tiddata 块,然后退出。不过建议不要调用 _endthreadex 函数。
一旦数据块被初始化并且与线程联系起来,线程调用的任何需要单线程实例数据的 C/C ++ 运行期库函数 都能很容易地(通过 TlsGetValue )检索调用线程的数据块地址,并对线程的数据进行操作。
当一个线程调用要求 tiddata 结构的 C/C++ 运行期库函数 时,将会发生下面的一些情况(大多数 C/C++ 运行期库函数 都是线程安全函数,不需要该结构)。首先, C/C++ 运行期库函数 试图 ( 通过调用 TlsGetValue) 获取线程的数据块的地址。如果返回 NULL 作为 tiddata 块的地址,调用线程就不拥有与该地址相关的 tiddata 块。这时, C/C ++ 运行期库函数 就在现场为调用线程分配一个 tiddata 块,并对它进行初始化。然后该 tiddata 块(通过 TlsSetValue )与线程相关联。此时,只要线程在运行,该 tiddata 将与线程待在一起。这时, C/C++ 运行期库函数 就可以使用线程的 tiddata 块,而且将来被调用的所有 C/C + + 运行期函数也能使用 tiddata 块。
这看来有些奇怪,因为线程运行时几乎没有任何障碍。实际上还是存在一些问题。
首先,如果线程使用 C/C++ 运行期库的 signal 函数,那么整个进程就会终止运行,因为结构化异常处理帧 尚未准备好(见上面: _ threadstartex 的一些重点) 。
第二,如果不是调用 _endthreadex 来终止线程的运行,那么数据块就不会被撤消,内存泄漏就会出现(因为没有人会调用 CreateThread 函数创建的线程,而调用 _endthreadex 终止线程)。
如果程序模块链接到多线程 DLL 版本的 C/C++ 运行期库,那么当线程终止运行并释放 tiddat 块(如果已经分配了 tiddata 块的话)时,该运行期库会 收到一个 DLL_THREAD_DETACH 通知。尽管这可以防止 tiddata 块的泄漏,但是强烈建议使用 _bdginthreadex 而不是使用 Createthread 来创建线程。
本文地址:http://www.cnblogs.com/fangyukuan/archive/2010/09/02/1816104.html