在Windows的多线程编程中,建立线程的函数主要有CreateThread,_beginthead(_beginthreadex)和AfxBeginThread,那么它们之间有什么联系与区别呢?当我须要建立一个线程时该用哪一个函数呢?编程
下面先介绍各个函数的用法:安全
CreateThread:数据结构
函数原型:多线程
参数:
lpThreadAttributes: 指向一个LPSECURITY_ATTRIBUTES结构的指针决定返回的句柄可否被继承,若是lpThreadAttributes为空,这个句柄不能被继承。
sdStackSize:初始化的堆栈大小,以字节为单位。若是为0,使用默认的大小。
lpStartAddress:函数的入口地址,是一个函数指针。
lpParameter:一个指针,被传递到线程函数里。
dwCreationFlags:线程建立的标志。若是为CREATE_SUSPENDED这个标志,那么须要使用ResumeThread函数来激活线程函数,若是为NULL,线程函数马上执行。
IpThreadId:一个指向线程id的指针,若是为空,线程id不被返回。
返回值:
1:若是函数成功执行,返回值将是这个新线程的句柄。若是失败,返回值是NULL。
2:当线程函数的起始地址无效(或者不可访问)时,CreateThread函数仍可能成功返回。若是该起始地址无效,则当线程运行时,异常将发生,线程终止,并返回一个错误代码,可使用GetLastError获取。
说明:
1:若是线程函数return,返回值会隐式条用ExitThread函数,可使用GetExitCodeThread函数得到该线程函数的返回值。
2:使用CreateThread建立的线程具备THREAD_PRIORITY_NORMAL线程优先级。可使用GetThreadPriority和SetThreadPriority函数获取和设置线程优先级值。
3:当一个线程结束时,这个线程的对象将得到有信号状态,使得任何等待这个对象的线程都可以成功并继续执行下去。
4:系统中的线程对象一直存活到线程结束,而且全部指向它的句柄都须要经过调用CloseHandle关闭。
5:若是一个线程调用了CRT,应该使用_beginthreadex 和_endthreadex(须要使用多线程版的CRT)。函数
_beginthread与_beginthreadex:
函数原型:ui
参数:
start_address:线程函数的入口地址。对于_beginthread,线程函数的调用约定是_cdecl。对于_beginthreadex,线程函数的调用约定是_stdcall。
stack_size:线程堆栈大小,能够为0。
arglist:传递给线程函数的参数,能够为0。
security:线程安全属性。
initflag:线程建立的初始标志。为CREATE_SUSPENDED则挂起线程,使用ResumeThread激活线程,为NULL则当即执行。
thrdaddr:线程Id。
返回值:
1:若是成功,将会返回一个新的线程句柄。然而,若是线程函数执行的很快,_beginthread可能获得一个非法的句柄。
2:若是失败,_beginthread返回-1,此时errno变量将被设置。_beginthreadex返回0,此时errno和_doserrno都被设置。
说明:
1:_beginthread函数的线程入口函数必须使用_cdecl调用约定。_beginthreadex函数的线程入口函数必须使用_stdcall调用约定
2:使用_beginthreadex比使用_begingthread更加安全。由于_beginthread的线程函数可能执行很快,这时可能会返回一个非法的句柄。
3:_endthread将会自动的关闭线程句柄,然而_beginthreadex不会,须要使用CloseHandle现实的关闭句柄。因此_beginthreadex函数可使用WaitForSingleObject函数来获取线程对象来进行同步。
4:一个链接Libcmt.lib的可执行文件,不要调用ExitThread函数,这个函数会阻止系统的运行时回收已分配的资源。使用_endthread and _endthreadex能够回收已分配的资源而后再调用ExitThread.
5: 能够调用_endthread和_endthreadex显示式结束一个线程。然而,当线程函数返回时,_endthread和_endthreadex 被自动调用。endthread和_endthreadex的调用有助于确保分配给线程的资源的合理回收。
6:当_beginthread和_beginthreadex被调用时,操做系统本身处理线程栈的分配。若是在调用这些函数时,指定栈大小为0,则操做系统 为该线程建立和主线程大小同样的栈。若是任何一个线程调用了abort、exit或者ExitProcess,则全部线程都将被终止。
7:对于使用C运行时库里的函数的线程应该使用_beginthread和_endthread这些C运行时函数来管理线程,而不是使用CreateThread和ExitThread。不然,当调用ExitThread后,可能引起内存泄露。
8:必须使用多线程版的 C run_time libraries.spa
AfxBeginThread:
函数原型:操作系统
参数:
pfnThreadProc:线程函数的入口地址。
函数原型:UINT __cdecl MyControllingFunction( LPVOID pParam );
pThreadClass:继承CWinThread类的RUNTIME_CLASS对象。
pParam: 传递给线程函数的参数,能够为0。
nPriority:线程优先级。
nStackSize:指明线程堆栈的大小,以字节为单位,能够为0。
dwCreateFlags:线程建立标志。
lpSecurityAttrs:线程安全属性。
返回值:
若是成功则返回一个指针指向线程对象,不然为NULL。
说明:
能够调用AfxEndThread来终止线程或者return。
.net
下面来介绍下这几个函数的联系与区别:
CreateThread:
CreateThread是Windows的API函数,提供操做系统级别的建立线程的操做。_beginthread(及_beginthreadex)与AfxBeginThread的底层实现都调用了CreateThread函数。
CreateThread函数没有考虑到下面二点:
(1)C Runtime中须要对多线程进行记录和初始化,以保证C函数库工做正常(典型的例子就是strtok函数)
(2)MFC也须要知道新线程的建立,也须要作一些初始化工做。
因此,在不调用MFC和CRT的函数时,能够用CreateThread建立线程,其它状况不要使用。
AfxBeginThread:
MFC中线程建立的函数,首先建立了相应的CWinThread对象,而后调用CWinThread::CreateThread,在CWinThread::CreateThread中完成了对线程对象的初始化工做,而后调用_beginthreadex建立线程。注意不要在一个MFC程序中使用_beginthreadex()或CreateThread()。
_beginthread和_beginthreadex: (实现文件分别是thread.c和threadex.c)
是MS对C Runtime库的扩展SDK函数,首先对C Runtime库作了一些初始化的工做,以保证C Runtime库工做正常。而后,调用CreateThread真正建立线程。
若要使多线程C和C++程序可以正确地运行,必须建立一个数据结构,并将它与使用C/C++运行期库函数的每一个线程关联起来。当你调用C/C++运行期库时,这些函数必须知道查看调用线程的数据块,这样就不会对别的线程产生不良影响。
1.每一个线程均得到由C/C++运行期库的堆栈分配的本身的tiddata内存结构。
2.传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。(指向tiddata结构的指针会做为一个TLS保存起来)
3._beginthreadex确实从内部调用CreateThread,由于这是操做系统了解如何建立新线程的惟一方法。
4.当调用CreatetThread时,它被告知经过调用_threadstartex而不是pfnStartAddr来启动执行新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。
5.若是一切顺利,就会像CreateThread那样返回线程句柄。若是任何操做失败了,便返回 NULL。
总结:
1:CreateThread是由操做系统提供的接口,而AfxBeginThread和_BeginThread则是编译器对它的封装。
2:用_beginthreadex()、_endthreadex函数应该是最佳选择,且都是C Run-time Library中的函数,函数的参数和数据类型都是C Run-time Library中的类型,这样在启动线程时就不须要进行Windows数据类型和C Run-time Library中的数据类型之间的转化,从而减低了线程启动时的资源消耗和时间的消耗。
3:MFC也是C++类库(只不过是Microsoft的C++类库,不是标准的C++类库),在MFC中也封装了new和delete两中运算符,因此用到new和delete的地方不必定非要使用_beginthreadex() 函数,用其余两个函数均可以。
4:_beginthreadex和_beginthread在回调入口函数以前进行一些线程相关的CRT的初始化操做。CRT的函数库在线程出现以前就已经存在,因此原有的CRT不能真正支持线程,这也致使了许多CRT的函数在多线程的状况下必须有特殊的支持,不能简单的使CreateThread就能够。
5:若是要做多线程(非MFC)程序,在主线程之外的任何线程内
使用malloc(),free(),new
调用stdio.h或io.h,包括fopen(),open(),getchar(),write(),printf(),errno
使用浮点变量和浮点运算函数
调用那些使用静态缓冲区的函数如: asctime(),strtok(),rand()等。
应该使用多线程的CRT并配合_beginthreadex(该函数只存在于多线程CRT), 其余状况,你可使用单线程的CRT并配合CreateThread。由于对产生的线程而言,_beginthreadex比CreateThread会为上述操做多作额外的工做,好比帮助strtok()为每一个线程准备一份缓冲区。
然而多线程程序极少状况不使用上述那些函数(好比内存分配或者io),因此与其每次都要思考是要使用_beginthreadex仍是CreateThread,不如就一棍子敲定_beginthreadex。
6:你也许会借助win32来处理内存分配和io,这时候你确实能够以单线程crt配合CreateThread,由于io的重任已经从crt转交给了win32。这时一般你应该使用HeapAlloc,HeapFree来处理内存分配,用CreateFile或者GetStdHandle来处理io。
7:还有一点比较重要的是_beginthreadex传回的虽然是个unsigned long,实际上是个线程Handle(事实上_beginthreadex在内部就是调用了CreateThread),因此你应该用CloseHandle来结束他。千万不要使用ExitThread()来退出_beginthreadex建立的线程,那样会丧失释放簿记数据的机会,应该使用_endthreadex.
下面对两个概念进行阐述
CRT(C/C++ Runtime Library):
是一种函数库,由编译器的生产厂家提供头文件或接口,操做系统提供运行时库的实现。因此Windows和Linux系统的运行时库函数接口虽然同样,但具体实现不同。
CRT是支持C/C++运行的一系列函数和代码的总称,虽然没有一个很精确的定义,可是能够知道,你的main函数就是它负责调用的,还有平时使用的strlen,strtok,time,atoi之类的函数也是它提供的。
线程局部存储(TLS,thread local storage)
一个多线程程序中,全局变量(及分配的内存)被全部线程所共享。函数的静态局部变量也被全部使用该函数的线程所共享。一个函数中的自动变量对每个线程是惟一的,由于它们存储于堆栈上,而每一个线程都有他们本身的堆栈。有时,咱们须要对每个线程惟一的持续性存储。例如,C函数strtok就须要这种存储。不幸的是,C语言不支持这种变量。可是Windows提供了四个API函数来实现这种机制。咱们把这种存储称为线程局部存储(TLS,Thread Local Storage)。
首先,定义一个结构,把对每一个线程惟一的数据包含在该结构中。
例如:
typedef struct
{
int one;
int two;
} DATA, *PDATA;
而后,主线程调用TlsAlloc函数来为进程得到一个TLS索引:tlsIndex = TlsAlloc();该TLS索引能够存储于一个全局变量或者经过线程函数的参数传递给其它线程。每一个须要使用该TLS索引的线程,先动态分配内存,而后调用TlsSetValue函数将该内存关联到该TLS索引(及该线程): TlsSetValue(tlsIndex, GlobalAlloc(GPTR, sizeof(DATA));
此时,线程直接或间接调用的函数能够经过以下方式得到该线程的TLS存储区域:
PDATA pdata;
pdata = (PDATA) TlsGetValue(tlsIndex);
此时,就可使用该线程的TLS存储区的变量了。
当线程函数终止时,它应该释放它所分配的动态空间: GlobalFree(TlsGetValue(tlsIndex));
当全部使用TLS的线程都终止后,主线程应当释放该TLS存储空间: TlsFree(tlsIndex);
TLS能够以一种更简单的方式使用,那就是经过Winodws对C所做的扩展关键字__declspec和扩展存储类型修饰符thread。例如:
__declspec(thread) int global_tls_i = 1; // 在函数外部,声明一个TLS变量
__declspec(thread) static int local_tls_i = 2; // 在函数内部声明一个静态TLS变量线程