createthread、_beginthread和_beginthreadex

1.MSDN

The _beginthread function creates a thread that begins execution of a routine at start_address. The routine at start_address must use the __cdecl calling convention and should have no return value. When the thread returns from that routine, it is terminated automatically. For more information about threads, see Multithreading.

_beginthreadex resembles the Win32 CreateThread API more closely than _beginthread does. _beginthreadex differs from _beginthread in the following ways:

  • _beginthreadex has three additional parameters: initflag, security, and threadaddr. The new thread can be created in a suspended state, with a specified security (Windows NT only), and can be accessed using thrdaddr, which is the thread identifier.

  • The routine at start_address passed to _beginthreadex must use the __stdcall calling convention and must return a thread exit code.

  • _beginthreadex returns 0 on failure, rather than -1L.

  • A thread created with _beginthreadex is terminated by a call to _endthreadex.

The _beginthreadex function gives you more control over how the thread is created than _beginthread does. The _endthreadex function is also more flexible. For example, with _beginthreadex, you can use security information, set the initial state of the thread (running or suspended), and get the thread identifier of the newly created thread. You are also able to use the thread handle returned by _beginthreadex with the synchronization APIs, which you cannot do with _beginthread.

It is safer to use _beginthreadex than _beginthread. If the thread generated by _beginthread exits quickly, the handle returned to the caller of _beginthread might be invalid or, worse, point to another thread. However, the handle returned by _beginthreadex has to be closed by the caller of _beginthreadex, so it is guaranteed to be a valid handle if _beginthreadex did not return an error.

You can call _endthread or _endthreadex explicitly to terminate a thread; however, _endthread or _endthreadex is called automatically when the thread returns from the routine passed as a parameter. Terminating a thread with a call to endthread or _endthreadex helps to ensure proper recovery of resources allocated for the thread.

_endthread automatically closes the thread handle (whereas _endthreadex does not). Therefore, when using _beginthread and _endthread, do not explicitly close the thread handle by calling the Win32 CloseHandle API. This behavior differs from the Win32 ExitThread API.


其实可以忽略_beginthread,只用关注_beginthreadex与CreateThread的区别(windows核心编程强烈建议不能用_beginthread


2._beginthreadex与CreateThread

《windows核心编程》
CreateThread函数是用于创建线程的Windows函数。不过,如果写的是C/C++代码,就绝对不要调用CreateThread。相反,正确的选择是使用Microsoft C++运行库函数_beginthreadex。如果使用的不是Microsoft C++编译器,你的编译器的提供商应该提供类似的函数来替代CreateThread。不管这个替代函数是什么,都必须使用它。本章稍后将解释_beginthreadex函数的用途及其重要性。


Visual Studio附带了4个原生的C/C++运行库,还有2个库面向Microsoft .NET的托管环境。注意,所有这些库都支持多线程开发:不再有单独的一个C/C++库专门针对单线程开发。

LibCMt.lib 库的静态链接Release版本
LibCMtD.lib 库的静态链接Debug版本
MSVCRt.lib 导入库,用于动态链接MSVCR80.dll 库的Release版本。 (这是新
建项目时的默认库)
MSVCRtD.lib 导入库,用于动态链接MSVCR80D.dll库的Debug版本
MSVCMRt.lib 导入库,用于托管/原生代码混合
MSVCURt.lib 导入库,编译成百分之百纯MSIL代码


由于标准C运行库是在1970年左右发明的。要在很久很久之后,才会在操作系统上出现线程的概念。标准C运行库的发明者根本没有考虑到为多线程应用程序使用C运行库的问题。(用了很多全局变量等,在多线程下会出问题)

在多线程环境中会出问题的C/C++运行库变量和函数有errno,_doserrno,strtok,_wcstok,strerror,_strerror,tmpnam,tmpfile,asctime,_wasctime,gmtime,_ecvt和_fcvt等等。

为了保证C和C++多线程应用程序正常运行,必须创建一个数据结构,并使之与使用了C/C++运行库函数的每个线程关联。然后,在调用C/C++运行库函数时,那些函数必须知道去查找主调线程的数据块,从而避免影响到其他线程。
那么,系统在创建新的线程时,是如何知道要分配这个数据块的呢?答案是它并不知道。系统并不知道应用程序是用C/C++来写的,不知道你调用的函数并非天生就是线程安全的。保证线程安全是程序员的责任。创建新线程时,一定不要调用操作系统的CreateThread函数。相反,必须调用C/C++运行库函数_beginthreadex。


换言之,我用自己能较好理解的语言再说一次,_beginthreadex是CRT的函数(虽然最终调用CreateThread实现,但并不是操作系统api,是微软的CRT实现)。多线程的问题只有在调用CRT才会出现(像用MFC调用createthread就不会出现问题),而OS本身是不会知道程序是用CRT还是MFC还是其他实现的(也不需要知道),而调用_beginthreadex就等于显式的跟操作系统说明我是在多线程中用了CRT,需要保证线程安全。


《windows核心编程》

用_beginthreadex 而不要用CreateThread 创建线程
你可能会好奇,假如调用CreateThread而不是C/C++运行库的_beginthreadex来创建新线程,会发生什么呢?当一个线程调用一个需要_tiddata结构的C/C++运行库函数时,会发生下面的情况。(大多数C/C++运行库函数都是线程安全的,不需要这个结构。)首先,C/C++运行库函数尝试取得线程数据块的地址(通过调用TlsGetValue)。如果NULL被作为_tiddata块的地址返回,表明主调线程没有与之关联的_tiddata块。在这个时候,C/C++运行库函数会为主调线程分配并初始化一个_tiddata块。然后,这个块会与线程关联(通过TlsSetValue) ,而且只要线程还在运行,这个块就会一直存在并与线程关联。现在,C/C++运行库函数可以使用线程的_tiddata块,以后调用的任何C/C++运行库函数也都可以使用。当然,这是相当诱人的,因为线程(几乎)可以顺畅运行。但事实上,问题还是有的。第一个问题是,假如线程使用了C/C++运行库的signal函数,则整个进程都会终止,因为结构化异常处理(SEH)帧没有就绪。第二个问题是,假如线程不是通过调用_endthreadex来终止的,数据块就不能被销毁,从而导致内存泄漏。(对于一个用CreateThread函数来创建的线程,谁会调用
_endthreadex呢?)

你可能感兴趣的:(createthread、_beginthread和_beginthreadex)