您的位置:首页 > 其它

_beginthreadex、CreateThread、AfxBeginThread

2013-08-14 13:54 330 查看
相关阅读:http://www.360doc.com/content/11/1129/22/1317564_168493874.shtml

1、CreateThread——Windows的API函数

2、_beginthreadex——MS对C Runtime库的扩展SDK函数

3、AfxBeginThread——MFC中线程创建的MFC函数

CreateThread

(API函数:SDK函数的标准形式,直截了当的创建方式,任何场合都可以使用。)

提供操作系统级别的创建线程的操作,且仅限于工作者线程。不调用MFC和RTL的函数时,可以用CreateThread,其它情况不要轻易。在使用的过程中要考虑到进程的同步与互斥的关系(防止死锁)。

线程函数定义为:DWORD WINAPI _yourThreadFun(LPVOID pParameter)。

但它没有考虑:

(1)C Runtime中需要对多线程进行纪录和初始化,以保证C函数库工作正常(典型的例子是strtok函数)。

(2)MFC也需要知道新线程的创建,也需要做一些初始化工作(当然,如果没用MFC就没事了)。

_beginthreadex

MS对C Runtime库的扩展SDK函数,首先针对C Runtime库做了一些初始化的工作,以保证C Runtime库工作正常。然后,调用CreateThread真正创建线程。 仅使用Runtime Library时,可以用_BegingThread。

AfxBeginThread

MFC中线程创建的MFC函数,首先创建了相应的CWinThread对象,然后调用CWinThread::CreateThread, 在CWinThread::CreateThread中,完成了对线程对象的初始化工作,然后,调用_beginthreadex(AfxBeginThread相比较更为安全)创建线程。它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意不要在一个MFC程序中使用_beginthreadex()或CreateThread()。

线程函数定义为:UINT _yourThreadFun(LPVOID pParam)

=====================================================================

【参考2】CreateThread与_beginthreadex

=====================================================================

CreateThread

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

_beginthreadex

"_beginthreadex函数的参数列表与CreateThread函数的参数列表是相同的,但是参数名和类型并不完全相同。这是因为 Microsoft的C/C++运行期库的开发小组认为, C/C++运行期函数不应该对Windows数据类型有任何依赖。_beginthreadex函数也像CreateThread那样,返回新创建的线程的句柄。因此,如果调用源代码中的CreateThread,就很容易用对_beginthreadex的调用全局取代所有这些调用。不过,由于数据类型并不完全相同,所以必须进行某种转换,使编译器运行得顺利些。"

"下面是关于_beginthreadex的一些要点:

1) 每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的Visual C++源代码中)。

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

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

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

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

为什么?

  “也许你想知道,如果调用CreateThread,而不是调用C/C++运行期库的_beginthreadex来创建新线程,将会发生什么情况。事实是,创建的线程调用 要求tiddata结构的C/C++运行期库函数时,将会发生下面的一些情况(大多数C/C++运行期库函数都是线程安全函数,不需要该结构)。

  首先,C/C++运行期库函数试图(通过调用TlsGetValue)获取线程的数据块的地址。如果返回NULL作为tiddata块的地址,调用线程就不拥有与该地址相关的tiddata块。这时,C/C++运行期库函数就在现场为调用线程分配一个tiddata块,并对它进行初始化。然后该
tiddata块(通过TlsSetValue)与线程相关联。此时,只要线程在运行,该tiddata将与线程待在一起。这时,C/C++运行期库函数就可以使用线程的tiddata块,而且将来被调用的所有C/C++运行期函数也能使用tiddata块。

  当然,这看来有些奇怪,因为线程运行时几乎没有任何障碍。不过,实际上还是存在一些问题。首先,如果线程使用C/C++运行期库的signal函数,那么整个进程就会终止运行,因为结构化异常处理帧尚未准备好。第二,如果不是调用_endthreadex来终止线程的运行,那么数据块就不会被撤消,内存泄漏就会出现(那么谁还为使用CreateThread函数创建的线程来调用_endthreadex呢?)。

   注意如果程序模块链接到多线程DLL版本的C/C++运行期库,那么当线程终止运行并释放tiddata块(如果已经分配了tiddata块的话)时,该运行期库会收到一个DLL_THREAD_DETACH通知。尽管这可以防止tiddata块的泄漏,但是强烈建议使用_beginthreadex而不是使用Createthread来创建线程。

=====================================================================

【参考3】关于_beginthreadex和CreateThread的区别

=====================================================================

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

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

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

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

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

  运行时库函数试图TlsGetv alue获取线程数据块的地址,如果没有获取到,函数就会现场分配一个 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()

=====================================================================

【参考4】_beginthreadex、CreateThread、AfxBeginThread的选择=====================================================================

1. Create/EndThread是Win32方法开始/结束一个线程

_beginthreadx/_endthreadex是C RunTime方式开始/结束一个线程

AfxBeginThread在MFC中开始/结束一个线程

2.直接在CreateThread API创建的线程中使用sprintf,malloc,strcat等涉及CRT存储堆操作的CRT库函数是很危险的,容易造成线程的意外中止。 在使用_beginthread和_beginthreadex创建的线程中可以安全的使用CRT函数。但是必须在线程结束的时候相应的调用_endthread或_endthreadex

3._beginthread成对调用的_endthread函数内部隐式的调用CloseHandle关闭了线程句柄,而与_beginthreadex成对使用的_endthreadex则没有关闭线程的句柄,需要显示的调用CloseHandle关闭线程句柄,不要使用_beginthread,使用._beginthreadex代替之。

4.尽量不要在一个MFC程序中使用_beginthreadex()或CreateThread()。

5.没有使用到MFC的线程尽量用_beginthreadex启动。

6.如果在一个与LIBCMT.LIB链接的程序中调用C Runtime函数,则必须要用_beginthreadex启动线程

7._beginthreadex启动的线程可以安全的调用任何C Runtime 函数

=====================================================================

【参考5】CreateThread()、_beginthread()以及_beginthreadex()联系与区别

=====================================================================

<<Windows核心编程>>中有很详细地介绍:

注意:若要创建一个新线程,绝对不要使用CreateThread,而应使用_beginthreadex.

Why?考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。全局变量用于多线程会出什么事,你一定知道的了。故必须存在一种机制,使得每个线程能够引用它自己的errno变量,又不触及另一线程的errno变量._beginthreadex就为每个线程分配自己的tiddata内存结构。该结构保存了许多像errno这样的变量和函数的值、地址(自己看去吧)。

通过线程局部存储将tiddata与线程联系起来。具体实现在Threadex.c中有。

结束线程使用函数_endthreadex函数,释放掉线程的tiddata数据块。

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

这样的提示!

转自:/article/6539850.html

========================================================

在Windows的多线程编程中,创建线程的函数主要有CreateThread,_beginthead(_beginthreadex)和AfxBeginThread,那么它们之间有什么联系与区别呢?当我需要创建一个线程时该用哪个函数呢?

下面先介绍各个函数的用法:

CreateThread:

函数原型:

HANDLE WINAPI CreateThread(

_in LPSECURITY_ATTRIBUTES lpThreadAttributes,

_in SIZE_T dwStackSize,

_in LPTHREAD_START_ROUTINE lpStartAddress,

_in LPVOID lpParameter,

_in DWORD dwCreationFlags,

_out LPDWORD lpThreadId

);

参数:

lpThreadAttributes: 指向一个LPSECURITY_ATTRIBUTES结构的指针决定返回的句柄能否被继承,如果lpThreadAttributes为空,这个句柄不能被继承。

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

lpStartAddress:函数的入口地址,是一个函数指针。

函数原型:DWORD
WINAPI ThreadProc( [in] LPVOID lpParameter);

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:

函数原型:

uintptr_t _beginthread(

void( *start_address )( void * ),

unsigned stack_size,

void *arglist

);

uintptr_t _beginthreadex(

void *security,

unsigned stack_size,

unsigned ( *start_address )( void * ),

void *arglist,

unsigned initflag,

unsigned *thrdaddr

);

参数:

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.

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++运行期库函数的每个线程关联起来。当你调用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()等。

然而多线程程序极少情况不使用上述那些函数(比如内存分配或者io),所以与其每次都要思考是要使用_beginthreadex还是CreateThread,不如就一棍子敲定_beginthreadex。

PDATA pdata;

pdata = (PDATA) TlsGetValue(tlsIndex);

__declspec(thread) int global_tls_i = 1; // 在函数外部,声明一个TLS变量

__declspec(thread) static int local_tls_i = 2; // 在函数内部声明一个静态TLS变量

应该使用多线程的CRT并配合_beginthreadex(该函数只存在于多线程CRT), 其他情况你,可以使用单线程的CRT并配合CreateThread。因为对产生的线程而言,_beginthreadex比CreateThread会为上述操作多做额外的工作,比如帮助strtok()为每个线程准备一份缓冲区。

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存储区域:

此时,就可以使用该线程的TLS存储区的变量了。

当线程函数终止时,它应该释放它所分配的动态空间: GlobalFree(TlsGetValue(tlsIndex));

当所有使用TLS的线程都终止后,主线程应当释放该TLS存储空间: TlsFree(tlsIndex);

TLS可以以一种更简单的方式使用,那就是通过Winodws对C所作的扩展关键字__declspec和扩展存储类型修饰符thread。例如:

转自:/article/7099609.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: