首页 > 编程知识 正文

afxendthread(generate与create的区别)

时间:2023-05-06 02:25:42 阅读:74652 作者:50

在Windows多线程编程中,创建线程的函数主要有CreateThread、_beginthead、_beginthreadex、AfxBeginThread,它们之间是如何进行的如果需要创建线程,应该使用哪个函数? 首先,说明各函数的使用方法。

CreateThread :

函数原型:

handlewinapicreatethread (inlpsecurity _ attributeslpthreadattributes,_ in size _ t dw堆栈大小,_inlpthread_staread _in LPVOID lpParameter,_in DWORD dwCreationFlags,_out LPDWORD lpThreadId; 参数:

lpThreadAttributes:确定返回的句柄是否由指向LPSECURITY_ATTRIBUTES结构的指针继承。 如果lpThreadAttributes为空,则不会继承此句柄。

SD堆栈大小:初始化的堆栈大小。 字节单位。 如果为0,则使用默认大小。

lpStartAddress :函数的条目地址。 是函数指针。

lpParameter :传递给线程函数的指针。

dwCreationFlags :线程创建的标志。 对于名为CREATE_SUSPENDED的标志,必须使用ResumeThread函数激活线程函数。 如果为NULL,则线程函数立即执行。

IpThreadId :指向线程id的指针。 如果为空,则不返回线程id。

返回值:

1 :如果函数成功执行,返回值将是此新线程的句柄。 如果失败,则返回值为NULL。

2 :即使线程函数的第一个地址无效(或不可访问),CreateThread函数也可能恢复正常。 如果此起始地址无效,则线程运行时会引发异常,线程终止,并返回可使用GetLastError检索的错误代码。

说明:

1 :对于线程函数return,返回值隐式使用退出thread函数。 可以使用GetExitCodeThread函数获取该线程函数的返回值。

使用CreateThread创建的线程具有THREAD_PRIORITY_NORMAL线程的优先级。 可以使用GetThreadPriority和SetThreadPriority函数获取和设置线程优先级值。

3 )线程结束后,该线程中的对象将变为信号状态,等待该对象的所有线程都将成功,并且可以继续运行。

4 :系统中的线程对象必须生存到线程结束,并且指向它的所有句柄都必须调用CloseHandle关闭。

5 )如果线程调用CRT,则必须使用_beginthreadex和_endthreadex。 (需要多线程版的CRT。

_beginthread和_beginthreadex:

函数原型:

uintptr _ t _ beginthread (void (start _ address ) void *,unsigned stack_size,void *arglist ); uintptr _ t _ beginthread ex (void * *安全,unsigned stack_size,unsigned ) start _ address (void * ) ) void * ) 参数:

start_address:线程函数的条目地址。 对于_beginthread,线程函数的调用规则为_cdecl。 对于_beginthreadex,线程函数的调用规则为_stdcall。

stack_size:线程堆栈大小。 可以为0。

arglist:传递给线程函数的参数可以是0。

安全:线程安全属性。

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.


AfxBeginThread:
函数原型:

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );参数:
pfnThreadProc:线程函数的入口地址。
 函数原型:UINT __cdecl MyControllingFunction( LPVOID pParam );
pThreadClass:继承CWinThread类的RUNTIME_CLASS对象。
pParam: 传递给线程函数的参数,可以为0。
nPriority:线程优先级。
nStackSize:指明线程堆栈的大小,以字节为单位,可以为0。
dwCreateFlags:线程创建标志。
lpSecurityAttrs:线程安全属性。
返回值:
如果成功则返回一个指针指向线程对象,否则为NULL。
说明:
可以调用AfxEndThread来终止线程或者return。

下面来介绍下这几个函数的联系与区别:
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++运行期库函数的每个线程关联起来。ymdxn调用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变量

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。