Windows线程:_beginthread与_beginthreadex与CreateThread C ++

什么是更好的方式来启动线程, _beginthread_beginthreadxCreateThread

我试图确定什么是_beginthread_beginthreadexCreateThread的优点/缺点。 所有这些函数都返回一个线程句柄给一个新创build的线程,我已经知道CreateThread在发生错误时提供了一些额外的信息(可以通过调用GetLastError来检查它)…但是我应该考虑什么使用这些function?

我正在使用Windows应用程序,所以跨平台兼容性已经不存在了。

我已经通过了msdn的文档,我不明白,为什么有人会决定使用_beginthread而不是CreateThread,反之亦然。

干杯!

更新:好的,谢谢所有的信息,我也读了几个地方,我不能调用WaitForSingleObject()如果我使用_beginthread() ,但如果我在线程调用_endthread()不应该工作? 那里有什么交易?

CreateThread()是一个原始的Win32 API调用,用于在内核级创build另一个控制线程。

_beginthread()_beginthreadex()是在后台调用CreateThread() C运行时库调用。 一旦CreateThread()返回, _beginthread/ex()负责额外的簿记操作,使C运行时库在新线程中可用且一致。

在C ++中,几乎可以肯定使用_beginthreadex()除非您根本不链接到C运行时库(又名MSVCRT * .dll / .lib)。

_beginthread()_beginthreadex()之间有几个区别。 _beginthreadex()更像CreateThread() (在两个参数中以及它的行为)。

正如Drew Hall提到的,如果你使用的是C / C ++运行时,你必须使用_beginthread() / _beginthreadex()而不是CreateThread()以便运行时有机会执行自己的线程初始化(设置线程本地存储等)。

实际上,这意味着CreateThread()几乎不会被代码直接使用。

_beginthread() / _beginthreadex()的MSDN文档有不同的细节 – 其中一个更重要的是,由于_beginthread()创build的线程的线程句柄在线程退出时由CRT自动closures,“如果由_beginthread生成的线程快速退出,返回给_beginthread的调用者的句柄可能是无效的,或者更糟糕的是,指向另一个线程”。

以下是CRT源代码中_beginthreadex()的注释:

 Differences between _beginthread/_endthread and the "ex" versions: 1) _beginthreadex takes the 3 extra parameters to CreateThread which are lacking in _beginthread(): A) security descriptor for the new thread B) initial thread state (running/asleep) C) pointer to return ID of newly created thread 2) The routine passed to _beginthread() must be __cdecl and has no return code, but the routine passed to _beginthreadex() must be __stdcall and returns a thread exit code. _endthread likewise takes no parameter and calls ExitThread() with a parameter of zero, but _endthreadex() takes a parameter as thread exit code. 3) _endthread implicitly closes the handle to the thread, but _endthreadex does not! 4) _beginthread returns -1 for failure, _beginthreadex returns 0 for failure (just like CreateThread). 

2013年1月更新

VS 2012的CRT在_beginthreadex()有一些额外的初始化操作:如果进程是一个“打包的应用程序”(如果GetCurrentPackageId()返回一些有用的东西),运行时会初始化新创build的线程上的MTA。

一般来说,正确的做法是调用_beginthread()/_endthread() (或ex()变体)。 但是,如果将CRT用作.dll,则CRT状态将被正确初始化并销毁,因为分别在调用CreateThread()ExitThread()或返回时,将使用DLL_THREAD_ATTACHDLL_THREAD_DETACH调用CRT的DllMain

CRT的DllMain代码可以在VC \ crt \ src \ crtlib.c的VS的安装目录中find。

这是_beginthreadex核心的代码(参见crt\src\threadex.c ):

  /* * Create the new thread using the parameters supplied by the caller. */ if ( (thdl = (uintptr_t) CreateThread( (LPSECURITY_ATTRIBUTES)security, stacksize, _threadstartex, (LPVOID)ptd, createflag, (LPDWORD)thrdaddr)) == (uintptr_t)0 ) { err = GetLastError(); goto error_return; } 

_beginthreadex的其余部分初始化CRT的每线程数据结构。

使用_beginthread*的优点是你的来自线程的CRT调用将正常工作。

您应该使用_beginthread_beginthreadex来允许C运行时库自行初始化线程。 只有C / C ++程序员需要知道这一点,因为他们现在应该使用自己的开发环境的规则。

如果你使用_beginthread你不需要调用CloseHandle因为RTL会为你做。 这就是为什么如果你已经使用_beginthread你不能等待的句柄。 另外_beginthread会导致混乱,如果线程函数立即(快速)退出,因为启动线程我留下一个无效的线程句柄到刚刚启动的线程。

_beginthreadex句柄可用于等待,但也需要显式调用CloseHandle 。 这是什么使他们安全地使用等待的一部分。 还有其他的问题,使它完全万无一失,总是启动线程暂停。 检查成功,logging处理等简历线程。 这是必要的,以防止线程在启动线程可以logging其句柄之前终止。

最好的做法是使用_beginthreadex ,开始挂起然后在录制句柄后恢复,等待句柄OK, CloseHandle必须被调用。

CreateThread()用于在代码中使用任何CRT函数时发生内存泄漏 。 _beginthreadex()CreateThread() _beginthreadex()具有相同的参数,它比_beginthread()更通用。 所以我build议你使用_beginthreadex()

关于你更新的问题:“如果我使用了_beginthread() ,但是如果我在线程中调用_endthread()不应该工作,我还读了几个我不能调用WaitForSingleObject()的地方?

通常,可以将线程句柄传递给WaitForSingleObject() (或等待对象句柄的其他API),直到线程完成。 但_beginthread()创build的线程句柄在_beginthread() _endthread()时被closures(这可以显式完成,或者在线程过程返回时由运行时隐式完成)。

问题在WaitForSingleObject()的文档中被调用:

如果这个句柄在wait等待的时候被closures了,那这个函数的行为是不确定的。

看看函数签名, CreateThread_beginthreadex几乎相同。

_beginthread_beginthreadxCreateThread

 HANDLE WINAPI CreateThread( __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in SIZE_T dwStackSize, __in LPTHREAD_START_ROUTINE lpStartAddress, __in_opt LPVOID lpParameter, __in DWORD dwCreationFlags, __out_opt LPDWORD lpThreadId ); 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 ); 

这里的说法_beginthread可以使用__cdecl__clrcall调用约定作为起点,而_beginthreadex可以使用__stdcall__clrcall作为起点。

我认为人们对CreateThread中的内存泄漏做出的评论已经有十多年的历史,应该被忽略。

有趣的是, _beginthread*函数实际上在我的机器上的C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src下调用了CreateThread

 // From ~line 180 of beginthreadex.c /* * Create the new thread using the parameters supplied by the caller. */ if ( (thdl = (uintptr_t) CreateThread( (LPSECURITY_ATTRIBUTES)security, stacksize, _threadstartex, (LPVOID)ptd, createflag, (LPDWORD)thrdaddr)) == (uintptr_t)0 ) { err = GetLastError(); goto error_return; } 

beginthreadex给你一个线程HANDLE用于WaitForSingleObject和朋友。 beginthread不。 完成后不要忘记CloseHandle() 。 真正的答案是使用boost::thread或很快C ++ 09的线程类。

_beginthread相比, _beginthread可以:

  1. 指定安全属性。
  2. 在暂停状态下启动一个线程。
  3. 你可以得到可以和OpenThread一起使用的线程ID。
  4. 如果调用成功,返回的线程句柄保证有效。 在那里你需要用CloseHandleclosuresCloseHandle
  5. 返回的线程句柄可以与同步API一起使用。

_beginthreadex类似于CreateThread ,但前者是CRT实现,后者是Windows API调用。 CreateThread的文档包含以下build议:

调用C运行时库(CRT)的可执行文件中的线程应该使用_beginthreadex_endthreadex函数进行线程pipe理,而不是CreateThreadExitThread ; 这需要使用CRT的multithreading版本。 如果使用CreateThread创build的线程调用CRT,则CRT可能会在内存不足的情况下终止进程。

CreateThread()曾经是否定的,因为CRT将不正确地初始化/清理。 但现在已经是历史了:现在可以(使用VS2010,可能还有几个版本)调用CreateThread()而不会破坏CRT。

这是官方的MS确认 。 它说明了一个例外:

实际上, CreateThread()创build的线程中不应该使用的唯一函数是signal()函数。

但是,从一致性的angular度来看,我个人更喜欢继续使用_beginthreadex()

CreateThread()是与语言CreateThread() Windows API调用。 它只是创buildOS对象 – 线程并返回HANDLE到这个线程。 所有的Windows应用程序都使用这个调用来创build线程。 所有的语言都避免了直接的API调用,原因很明显:1.你不希望你的代码是特定的操作系统2.在调用API之前,你需要做一些维护:转换参数和结果,分配临时存储等。

_beginthreadex()CreateThread() C封装,它占C的特定。 它通过分配特定于线程的存储,使原始的单线程C f-ns在multithreading环境中工作。

如果您不使用CRT,则无法避免直接调用CreateThread() 。 如果使用CRT,则必须使用_beginthreadex()或某些CRTstringf-ns在VC2005之前可能无法正常工作。

CreateThread()是直接的系统调用。 它在Kernel32.dll上实现,很可能你的应用程序已经被其他原因链接了。 它始终可用于现代Windows系统。

_beginthread()_beginthreadex()是Microsoft C运行时( msvcrt.dll )中的包装函数。 这两个电话之间的差异在文件中说明。 因此,当Microsoft C运行时可用时,或者如果您的应用程序静态链接到它,它是可用的。 除非你使用纯Windows API编写代码(正如我个人经常这样做的),否则你也可能会链接到这个库。

你的问题是一个连贯的,实际上反复出现的问题。 尽可能多的API在我们必须处理的Windows API中存在重复和模糊的function。 最糟糕的是,文件没有澄清这个问题。 我猜想_beginthread()系列函数是为了更好地与其他标准C函数集成而创build的,比如errno的操作。 _beginthread()因此与C运行时更好地集成。

尽pipe如此,除非你有充分的理由使用_beginthread()_beginthreadex() ,你应该使用CreateThread() ,主要是因为你可能会在最终的可执行文件中获得一个较less的库依赖项(对于MS CRT来说,这有点重要)。 通话周围也没有包装代码,虽然这种效果可以忽略不计。 换句话说,我认为坚持CreateThread()主要原因是没有很好的理由使用_beginthreadex()来开始。 function正是或几乎相同。

使用_beginthread()一个很好的理由 (如同它似乎是错误的),如果调用了_endthread() ,那么C ++对象将被正确地展开/销毁。

其他答案无法讨论调用包装Win32 API函数的C运行时函数的含义。 考虑DLL加载器locking行为时,这一点很重要。

不pipe_beginthread{ex}是否有特殊的C运行时线程/光纤内存pipe理,正如其他答案所讨论的那样,它是在(假设dynamic链接到C运行时)DLL中执行的,进程可能尚未加载。

DllMain调用_beginthread*是不安全的。 我已经通过编写使用Windows“AppInit_DLLs”function加载的DLL进行了testing。 调用_beginthreadex (...)而不是CreateThread (...)会导致Windows的许多重要部分在启动过程中停止运行,因为DllMain入口点死锁正在等待加载程序locking被释放,以便执行某些初始化任务。

顺便说一下,这也是为什么kernel32.dll有很多重叠的string函数,C运行时也DllMain – 使用DllMain函数来避免同样的情况。

如果您阅读“从Jeffrey RichterdebuggingWindows应用程序”一书,他解释说,几乎在所有情况下,您都必须调用_beginthreadex而不是调用CreateThread_beginthread只是_beginthread的简化包装。

_beginthreadex初始化CreateThread API不会执行的某些CRT(C RunTime)内部消息。

如果使用CreateThread API而不是使用_begingthreadex调用CRT函数,结果可能会导致意想不到的原因问题。

看看这个旧的微软杂志从里希特。

两者之间没有任何区别。

所有有关内存泄漏的评论都是基于很老的<VS2005版本。 几年前我已经做了一些压力testing,可以揭穿这个神话。 即使微软在他们的例子中混合了样式,几乎从不使用_beginthread。