Windows线程:_beginthread与_beginthreadex与CreateThread C ++
什么是更好的方式来启动线程, _beginthread
, _beginthreadx
或CreateThread
?
我试图确定什么是_beginthread
, _beginthreadex
和CreateThread
的优点/缺点。 所有这些函数都返回一个线程句柄给一个新创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_ATTACH
和DLL_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
, _beginthreadx
和CreateThread
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
可以:
- 指定安全属性。
- 在暂停状态下启动一个线程。
- 你可以得到可以和
OpenThread
一起使用的线程ID。 - 如果调用成功,返回的线程句柄保证有效。 在那里你需要用
CloseHandle
closuresCloseHandle
。 - 返回的线程句柄可以与同步API一起使用。
_beginthreadex
类似于CreateThread
,但前者是CRT实现,后者是Windows API调用。 CreateThread的文档包含以下build议:
调用C运行时库(CRT)的可执行文件中的线程应该使用
_beginthreadex
和_endthreadex
函数进行线程pipe理,而不是CreateThread
和ExitThread
; 这需要使用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。