第一、CreateThread
CreateThread是Windows的API函数(SDK函数的标准形式,直截了当的创建方式,任何场合都可以使用),提供操作系统级别的创建线程的操作,且仅限于工作者线程。不调用MFC和RTL函数(标准C语言函数),即只调用win32API时,可以用CreateThread,其它情况不要轻易。在使用的过程中要考虑到进程的同步与互斥的关系(防止死锁)。
线程函数定义为:DWORD WINAPI _yourThreadFun(LPVOID>CreateThread)
缺点:没有考虑:
(1)C Runtime中需要对多线程进行纪录和初始化,以保证C函数库工作正常(典型的例子是strtok函数)。
(2)MFC也需要知道新线程的创建,也需要做一些初始化工作(当然,如果没用MFC就没事了)。
第二、 AfxBeginThread
AfxBeginThread是MFC中线程创建的MFC函数,首先创建了相应的CWinThread对象,然后调用CWinThread::CreateThread, 在CWinThread::CreateThread中,完成了对线程对象的初始化工作,然后,调用_beginthreadex(AfxBeginThread相比较更为安全)创建线程。它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意不要在一个MFC程序中使用_beginthreadex()或CreateThread()。
线程函数定义为:UINT _yourThreadFun(LPVOID pParam)
第三、_beginthreadex
_beginthreadex是MS对C Runtime库的扩展SDK函数,首先针对C Runtime库做了一些初始化的工作,以保证C Runtime库工作正常。然后,调用CreateThread真正创建线程。仅使用Runtime Library时,可以用_BegingThread。
uintptr_t _beginthread(
void( __cdecl *start_address )( void * ),
unsigned stack_size,
void *arglist
);
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
//第1个参数:安全属性,NULL为默认安全属性
//第2个参数:指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0
//第3个参数:指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址)
//第4个参数:传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针
//第5个参数:线程初始状态,0:立即运行;CREATE_SUSPEND:suspended(悬挂) //第6个参数:用于记录线程ID的地址
_beginthreadex( NULL, 0, threadProc, &pagram, 0,(unsigned int *) idThread );
注意:
_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址,如下:
void _cdecl XXX(vooid *);
unsinged int stdcall(void *);
_beginthreadex内部会自动调用 _endthreadex.
_beginthread内部会自动调用_endthread.
_endthread内部会自动调用CloseHandle关闭当前Thread内核对象的句柄,所以在用_beginthread 时我们不需要在主线程中调用CloseHandle来关闭子线程的句柄。 _endthreadex相比_endthread而言更安全。它不会自动关闭当前Thread内核对象的句柄。所以在用_beginthreadex时我们需要用CloseHandle来关闭子线程的句柄。
小节1:
实际上,这三个函数之间存在一定的调用关系,第一个纯粹一些,后两个完成自己相应的工作之后,调用前者实现线程的创建。其中CreateThread是由操作系统提供的接口,而AfxBeginThread和_BeginThread则是编译器对它的封装。
小节2:
用_beginthreadex()、_endthreadex函数应该是最佳选择,且都是C Run-time Library中的函数,函数的参数和数据类型都是C Run-time Library中的类型,这样在启动线程时就不需要进行Windows数据类型和C Run-time Library中的数据类型之间的转化,从而,减低了线程启动时的资源消耗和时间的消耗。但使用_beginthread,无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程ID,_endthread的情况类似,它不带参数,这意味这线程的退出代码必须硬编码为0。
MFC也是C++类库(只不过是Microsoft的C++类库,不是标准的C++类库),在MFC中也封装了new和delete两中运算符,所以用到new和delete的地方不一定非要使用_beginthreadex() 函数,用其他两个函数都可以。
_beginthreadex和_beginthread在回调入口函数之前进行一些线程相关的CRT的初始化操作。
小节3 其他问题:
1)CreateThread 使用不当引起内在泄露?
CreateThread 和 _beginthreadex 区别。在 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函数的直接调用。难道是权威错了??抱着怀疑的态度查找了大量的资料和书籍,终于搞明白了这个问题的关键所在,在此做个说明,算是对那句话的一个完善。
关于_beginthreadex和CreateThread的区别我就不做说明了,这个很容易找到的。我们只要知道一个问题:_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(),或是new和delete
2 使用stdio.h或io.h里面声明的任何函数
3 使用浮点变量或浮点运算函数
4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand()
有些CRT的函数象malloc(), fopen(), _open(), strtok(), ctime(), 或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread和ExitThread也无法知道这件事,于是就会有Memory Leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽!
因为对产生的线程而言,_beginthreadex比之CreateThread会为上述操作多做额外的簿记工作,比如帮助strtok()为每个线程准备一份缓冲区。
然而多线程程序极少情况不使用上述那些函数(比如内存分配或者io),所以与其每次都要思考是要使用_beginthreadex还是CreateThread,不如就一棍子敲定_beginthreadex。
当然你也许会借助win32来处理内存分配和Io,这时候你确实可以以单线程crt配合CreateThread,因为io的重任已经从crt转交给了win32。这时通常你应该使用HeapAlloc,HeapFree来处理内存分配,用CreateFile或者GetStdHandle来处理Io。
还有一点比较重要的是_beginthreadex传回的虽然是个unsigned long,其实是个线程Handle(事实上_beginthreadex在内部就是调用了CreateThread),所以你应该用CloseHandle来结束他。千万不要使用ExitThread()来退出_beginthreadex创建的线程,那样会丧失释放簿记数据的机会,应该使用_endthreadex.
2)_endthreadex使用注意事项
转自http://blog.csdn.net/kailan818/article/details/5928850
#include <string>
#include <process.h>
#include <windows.h>
#include <iostream>
using namespace std;
unsigned __stdcall th_ThreadA()
{
string a;
_endthreadex(0);
return 0;
}
这里如果显式调用_endthreadex则string会泄露,如果直接return则不会,但是为什么网上的示例代码包括msdn都显示调用_endthreadex,并且说这样可以防止内存泄露?
std::string的资源释放是由析构函数进行的,C++会保证但是析构函数是在return 以后发生的,这个代码return语句永远不会被运行,因为_endthreadex的存在,和exit是类似的。所以如果不是提前退出的话,你可以不写endthreadex,函数返回后系统会调用_endthreadex的,参见《Windows核心编程》。
如果你要提前退出的话,请使用大括号:
{
string a;
...
} // a released here
_endthreadex(0);
那既然任何需要退出的地方都能用return完成是不是endthreadex就没有显式调用的意义了?
应该是的吧,看msdn上也没说有调用的必要,return的时候会自动调用_endthreadex。如果你是 _beginthreadex创建的话调用_endthreadex是必须的,但不是指显示调用,这是我对msdn有关说明的理解。所谓必须是清理_beginthreadex为thread创建的tiddata。
要说_endthreadex完全没有用肯定是不对的,_endthreadex并不是一个过时的函数,正确的使用并不会带来问题。比如在线程的主函数中,return是_endthreadex的一个良好替代,就像main函数里面return是exit()或ExitProccess()的良好替代一样,但是这不表示exit函数没用。比如线程调用了一个子函数,如果子函数决定退出线程,return是没用的,_endthreadex即可终结线程。
但是这个设计不好,因为可能造成LZ提出的资源泄漏。尤其考虑到后台线程终结后的资源泄漏比主线程的资源泄漏更要命(主线程退出后,进程就退出了,OS会清理一切资源,无所谓泄露不泄露,而子线程退出后主线程可能还会运行很久,并且可能有大量的同类型的子线程退出,会造成要命的泄露)。良好的设计还是返回线程的主函数,让threadproc来决定是不是要退出,从这个意义上说,_endthreadex没有必要。
微软也指出,有些程序员就是要调用exit系列函数(ExitThread,ExitProccess等),没辙,只好提供了。
转自:http://bbs.sjtu.edu.cn/bbscon,board,VC,file,M.1256696811.A.html