CreateThread()、_beginthreadex()及、AfxBeginThread()函数的讨论

 操作系统中线程是非常重要的概念,所以关于线程的创建常常有些困扰人的内容。好像创建线程的函数很多,那么他们之间的有什么联系与区别呢?正如题目给出的三个函数。今天看了看Windows核心编程,再找了一些网上的资料,在此想说说这些函数之间的关系和区别。如有不正确的地方,请各位不吝赐教。

      首先,需要说明的是这三个函数都与CreateThreadCreateThread函数是Windows的一个API函数,其具体的使用方法在 MSDN和《Windows核心编程》上都有详细介绍。主要的作用是创建一个线程。_beginthreadex函数是C/C++运行库提供的函数,从 _beginthreadex函数的源代码,可以看出它的主要动作是:增加了一个名为ptd_ptiddata的结构的处理,然后在调用CreateThread函数。_ptiddata是每个线程都拥有自己的专用的数据结构。关于使用CreateThread代替_beginthreadex的结果以及可能出现的问题在《Windows核心编程》上讲的很清楚:也许你想知道,如果调用CreateThread,而不是调用C/C++运行期库的_beginthreadex来创建新线程,将会发生什么情况。当一个线程调用要求_ptiddata结构的C / C + +运行期库函数时,将会发生下面的一些情况(大多数C / C + +运行期库函数都是线程安全函数,不需要该结构)。

       首先, C / C + +运行期库函数试图(通过调用T l s G e t Va l u e )获取线程的数据块的地址。如果返回N U L L作为t i d d a t a块的地址,调用线程就不拥有与该地址相关的_t i d d a t a块。这时,C / C + +运行期库函数就在现场为调用线程分配一个_t i d d a t a块,并对它进行初始化。然后该_t i d d a t a块(通过T l s S e t Va l u e)与线程相关联。此时,只要线程在运行,该_t i d d a t a将与线程待在一起。这时,C / C + +运行期库函数就可以使用线程的_t i d d a t a块,而且将来被调用的所有C / C + +运行期函数也能使用_t i d d a t a块。当然,这看来有些奇怪,因为线程运行时几乎没有任何障碍。不过,实际上还是存在一些问题。首先,如果线程使用C / C + +运行期库的s i g n a l函数,那么整个进程就会终止运行,因为结构化异常处理帧尚未准备好。

       第二,如果不是调用_ e n d t h r e a d e x来终止线程的运行,那么数据块就不会被撤消,内存泄漏就会出现(那么谁还为使用C r e a t e T h r e a d函数创建的线程来调用_ e n d t h r e a d e x呢?)。对于上面所说的两个问题:我也是有疑问的:使用CreateThread创建线程后,用CloseHandle函数关闭相应的线程句柄,不会对_ptiddata结构进行释放吗?另外在网上看到一些关于这三个函数的讨论如下:一直对这三个创建线程的方法都搞不清楚,不知道在什么情况下该用那种方法,下面是从网上摘录的一些帖子:

       1、不要在一个MFC程序中使用_beginthreadex()CreateThread()。这句话的意思是由于AfxBeginThread()MFC封装的启动线程的函数,里面包含了很多和MFC相关的启动信息,而且封装了一些常用的操作,使用起来也比较简便。而用另外两个函数就需要程序员对类型,安全性检查进行更多的思考!

       2、用_beginthreadex()函数应该是最佳选择,因为_beginthreadex()函数是CRun-timeLibrary中的函数,函数的参数和数据类型都是CRun-timeLibrary中的类型,这样在启动线程时就不需要进行Windows数据类型和CRun-timeLibrary中的数据类型之间的转化。减低了线程启动时的资源消耗和时间的消耗!

      3、在C程序中,几乎都要用到newdelete,难道只有使用_beginthreadex()?不,因为MFC也是C类库(只不过是MicrosoftC类库,不是标准的C类库),在MFC中也封装了newdelete两中运算符,所以用到newdelete的地方不一定非要使用_beginthreadex()函数,用其他两个函数都可以!其实在程序中使用上面的哪个函数并不是绝对的,书的作者只不过是提了一个更佳的搭配方法,我在MFC程序中也经常使用_beginthreadex()CreateThread()这两个函数,运行的效果也没有多大的区别,有的时候只是需要你额外的进行一些类型检查和其他的一些转化操作,其余没有其他不妥!创建线程只有一个方法是::CreateThread()_beginthreadex()AfxBeginThread()等内部都是调用这个函数的,因为操作系统只提供这一个接口C静态库比WINDOWS出来还早,就别提多线程了,所以他对多线程的支持不是很好,但后悔也来不急,但也不能怪人家。

      C运行库_beginthreadex()。他经过一些处理后,再调用CreateThread()如果要强制结束的话也最好用_endthreadex结束,因为他也要一些处理。总结上面的内容,当然《Windows核心编程》上面得说法是比较权威的。所以,在对线程的结构、运行还不是很了解的时候最好还是按照书上的来。这样能够避免一些可能出现的莫名奇妙的错误,也省去的一些其他结构处理的考虑。当你清楚地知道线程的结构与运行机制,以及了解各个函数对CreateThread函数的封装的时候,大概那时候就能够应用自如了。

 

 beginthread beginthreadex CreateThread 区别

  

建立一个线程。
unsigned long beginthread(void(cdecl *startaddress)(void*),unsigned stacksize,void *arglist);
unsigne dlong beginthreadex(void *security,unsignedstacksize,unsigned(stdcall*startaddress) (void *), void *arglist,unsignedinitflag,unsigned *thrdaddr);
例程 需要的头文件 兼容性
beginthread <process.h> Win NT,Win 95
beginthreadex <process.h> Win NT,Win 95
对于另外兼容性的信息,参见引言中的兼容性

LIBC.LIB 单线程静态库,零售版本
LIBCMT.LIB 多线程静态库,零售版本
MSVCRT.LIB MSVCRT.DLL的输入库,零售版本
为了使用beginthread或beginthreadex,该应用必须与多线程C运行库之一进行链接。
返回值
如果成功,这些函数返回最近建立的线程的句柄,出现一个错误时,beginthread返回-1在这种情况下如果有太多的线程,则errno设置为EAGAIN,如果参量无效或栈尺寸不正确,则errno设置为EINVAL。在出现一个错误时,beginthreadex返回0,这种情况下errnodoserrno都被设置。
参数
startaddress
开始执行新线程的例程的起始地址。
Stacksize
新线程的栈尺寸或0。
Arglist
传递给新线程的参量表或NULL。
Security
新线程的安全指示符;对于Windows 95应用必须为NULL。
Initflag
新线程的初始状态(运行时返回0或暂停时返回CREATESUSPEND)。
Thrdaddr
新函数的地址。
说明
beginthread函数建立一个线程,开始startaddress处例程的执行。在startaddress处的例程必须使用cdecl调用约定且没有返回值,当该线程从这个例程返回时,它自动终止。
.beginthredex比beginthread更紧密地汇编Win32Create ThreadAPI函数,beginthreadex在如下方面不同于beginthread:
.beginthreadex有另外三个参数:initflag、security和threadaddr。新线程可以在暂停状态中建立,使用指定的安全方式(仅在Windows NT下),可以使用thrdadar访问,它是线程标识符。
.startaddress处的程序传送给beginthreadex,必须使用stdcall调用约定且必须返回一个线程退出码。
失败时beginthreadex返回0,而不是-1。
.eginthreadex建立的线程通过调用endthreadex终止。
你可以显式调用endthread或endthreadex终止一个线程,但当该线程从作为参量传递的例程返回时自动调用endthread或endthreadex。通过调用endthread或endthreadex终止一个线程帮助确保恢复该线程分配的资源。
endthread自动关闭该线程句柄(而endthreadex不这样),因此,当使用beginthread和endthread时,通过调用Win32CloseHandle API函数并不显式关闭该线程句柄。这个行为不同于Win32 ExitThreadAPI函数。
注意:对于与LIBCMT.LIB链接的可执行文件,不要调用Win32ExitThread API函数;这防止该运行系统要求收回分配的资源。endthread和endthreadex要求收回分配的线程资源,然后调用ExitThread。在beginthread或beginthreadex被调用时,操作系统处理栈的分配你不需要传送线程栈地址给这些函数。另外,stacksize参量可以为0,在这种情况下操作系统使用与主线程中指定的栈相同的值。
arglist是传送给最近建立的线程的参数。它通常是一个数据项例如字符串的地址。
arglist如果不需要可以为NULL,但beginthread和beginthreadex必须提供一些传递给新线程的值。如果任何线程调用abort、exit、exit或ExitProcess,则所用线程被终止。

 在写c++代码时,一直牢记着一句话:决不应该调用CreateThread。相反,应该使用Visual  C++运行期库函数_beginthreadex。

  好像CreateThread函数就是老虎,既然这样为什么微软要开发这个函数呢?

  从网上找到的相关资料,现在汇总一下,在此对相关人员进行感谢!

  摘自《windows  核心编程》:  

  CreateThread函数是用来创建线程的Windows函数。不过,如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用Visual  C++运行期库函数_beginthreadex。如果不使用Microsoft的Visual  C++编译器,你的编译器供应商有它自己的CreateThred替代函数。  

  若要使多线程C和C++程序能够正确地运行,必须创建一个数据结构,并将它与使用C/C++运行期库函数的每个线程关联起来。当你调用C/C++运行期库时,这些函数必须知道查看调用线程的数据块,这样就不会对别的线程产生不良影响。 

  1.每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。  

  2.传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。 

  3._beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。 

  4.当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。   还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。 

  5.如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。 

  _beginthreadex和_beginthread函数的区别。_beginthread函数的参数比较少,因此比特性全面的_beginthreadex函数受到更大的限制。

例如,如果使用_beginthread,就无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。

  下面摘录Csdn中的Holly()的帖子进行解释,再次表示感谢。

  来源:http://topic.csdn.net/t/20000926/10/31810.html

  Holly():

  oldworm提供了很好的使用的例子,而且也运用了编译控制! 

  我来解释一下理论上的区别:  

  CreateThread、_beginthread和_beginthreadex都是用来启动线程的,但大家看到oldworm没有提供_beginthread的方式,原因简单,_beginthread是_beginthreadex的功能子集,虽然_beginthread内部是调用_beginthreadex但他屏蔽了象安全特性这样的功能,所以_beginthread与CreateThread不是同等级别,_beginthreadex和CreateThread在功能上完全可替代,我们就来比较一下_beginthreadex与CreateThread!  

  CRT的函数库在线程出现之前就已经存在,所以原有的CRT不能真正支持线程,这导致我们在编程的时候有了CRT库的选择,在MSDN中查阅CRT的函数时都有: 

 Libraries  
 LIBC.LIB  Single  thread  static  library, retail  version   
 LIBCMT.LIB  Multithread  static  library,  retail version   
 MSVCRT.LIB  Import  library  for  MSVCRT.DLL, retail  version 

  这样的提示!  

  对于线程的支持是后来的事!  

  这也导致了许多CRT的函数在多线程的情况下必须有特殊的支持,不能简单的使用CreateThread就OK。  

  大多的CRT函数都可以在CreateThread线程中使用,看资料说只有signal()函数不可以,会导致进程终止!但可以用并不是说没有问题! 

  有些CRT的函数象malloc(),  fopen(),  _open(),  strtok(),  ctime(),  或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread和ExitThread也无法知道这件事,于是就会有Memory  Leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽!  

  _beginthreadex(内部也调用CreateThread)和_endthreadex就对这个内存块做了处理,所以没有问题!(不会有人故意用CreateThread创建然后用_endthreadex终止吧,而且线程的终止最好不要显式的调用终止函数,自然退出最好!) 

  谈到Handle的问题,_beginthread的对应函数_endthread自动的调用了CloseHandle,而_beginthreadex的对应函数_endthreadex则没有,所以CloseHandle无论如何都是要调用的不过_endthread可以帮你执行自己不必写,其他两种就需要自己写!(Jeffrey  Richter强烈推荐尽量不用显式的终止函数,用自然退出的方式,自然退出当然就一定要自己写CloseHandle)

 

关于_beginthreadexCreateThread的区别

Win32 API中,创建线程的基本函数是CreateThread,而_beginthread(ex)
C++
运行库的函数。为什么要有两个呢?因为C++ 运行库里面有一些函数使用了全局
量,如果使用CreateThread 的情况下使用这些C++ 运行库的函数,就会出现不安全
的问题。而_beginthreadex 为这些全局变量做了处理,使得每个线程都有一份独立
全局量。

所以,如果你的编程只调用Win32 API/SDK ,就放心用CreateThread;如果要用到
C++
运行时间库,那么就要使用_beginthreadex ,并且需要在编译环境中选择 Use
MultiThread Lib/DLL


C++
运行期库有两个创建线程的函数,另一个是 _beginthread它们两者的区别请
自己去看MSDN

通常他们的解释都是这容易造成内存泄漏。这个解释本身是没有错的,但是解释得不够完全和详细。以至于造成很多新手盲目的信任了那句话,在那里都是用_beginthreadex函数,或者是装作没有看到使用CreateThread函数。曾经有一段时间我也对这个问题很是困惑,不知道到底用那个才是对的。因为我不止一次在很多权威性的代码中看到对CreateThread函数的直接调用。难道是权威错了??抱着怀疑的态度查找了大量的资料和书籍,终于搞明白了这个问题的关键所在,在此做个说明,算是对那句话的一个完善。

关于_beginthreadexCreateThread的区别我就不做说明了,这个很容易找到的。我们只要知道一个问题:_beginthreadex是一个C运行时库的函数,CreateThread是一个系统API函数,_beginthreadex内部调用了CreateThread。只所以所有的书都强调内存泄漏的问题是因为_beginthreadex函数在创建线程的时候分配了一个堆结构并和线程本身关联起来,我们把这个结构叫做tiddata结构,是通过线程本地存储器TLS于线程本身关联起来。我们传入的线程入口函数就保存在这个结构中。tiddata的作用除了保存线程函数入口地址之外,还有一个重要的作用就是:C运行时库中有些函数需要通过这个结构来保存和获取一些数据,比如说errno之类的线程全局变量。这点才是最重要的。

当一个线程调用一个要求tiddata结构的运行时库函数的时候,将发生下面的情况:

运行时库函数试图TlsGetvalue获取线程数据块的地址,如果没有获取到,函数就会现场分配一个 tiddata结构,并且和线程相关联,于是问题出现了,如果不通过_endthreadex函数来终结线程的话,这个结构将不会被撤销,内存泄漏就会出现了。但通常情况下,我们都不推荐使用_endthreadex函数来结束线程,因为里面包含了ExitThread调用。

找到了内存泄漏的具体原因,我们可以这样说:只要在创建的线程里面不使用一些要求tiddata结构的运行时库函数,我们的内存时安全的。所以,前面说的那句话应该这样说才完善:

绝对不要调用系统自带的CreateThread函数创建新的线程,而应该使用_beginthreadex,除非你在线程中绝不使用需要tiddata结构的运行时库函数

这个需要tiddata结构的函数有点麻烦了,在侯捷的《win32多线程程序设计》一书中这样说到:

如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用_beginthreadex_endthreadex

1 使用malloc()free(),或是newdelete

2 使用stdio.hio.h里面声明的任何函数

3 使用浮点变量或浮点运算函数

4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()rand()

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值