启动帖子的更好方法是_beginthread
,_beginthreadx
还是CreateThread
?
我正在尝试确定_beginthread
,_beginthreadex
和CreateThread
的优点/缺点。所有这些函数都返回一个新创建的线程的线程句柄,我已经知道CreateThread在发生错误时提供了一些额外的信息(可以通过调用GetLastError
来检查)...但是我有些什么我应该考虑何时使用这些功能?
我正在使用Windows应用程序,因此跨平台兼容性已经不可能了。
我已经阅读了msdn文档,我无法理解,例如,为什么有人会决定使用_beginthread而不是CreateThread,反之亦然。
干杯!
更新:
好的,感谢您提供的所有信息,如果我使用WaitForSingleObject()
,我也会在几个地方看到我无法拨打_beginthread()
,但如果我在线程中调用_endthread()
不应该这样吗?这是什么交易?
答案 0 :(得分:94)
CreateThread()
是一个原始的Win32 API调用,用于在内核级别创建另一个控制线程。
_beginthread()
& _beginthreadex()
是C运行时库调用,在后台调用CreateThread()
。一旦CreateThread()
返回,_beginthread/ex()
将负责额外的簿记,以使C运行时库可用并且在新线程中保持一致。
在C ++中,您几乎肯定会使用_beginthreadex()
,除非您根本不链接到C运行时库(也就是MSVCRT * .dll / .lib)。
答案 1 :(得分:36)
_beginthread()
和_beginthreadex()
之间存在一些差异。 _beginthreadex()
的行为更像CreateThread()
(在两个参数及其行为方式上)。
如Drew Hall所述,如果您正在使用C / C ++运行时,则必须使用_beginthread()
/ _beginthreadex()
而不是CreateThread()
,以便运行时有机会执行它自己的线程初始化(设置线程本地存储等)。
实际上,这意味着CreateThread()
几乎不会被您的代码直接使用。
_beginthread()
/ _beginthreadex()
的MSDN文档在差异上有相当多的细节 - 其中一个更重要的是,因为_beginthread()
创建的线程的线程句柄得到了当线程退出时由CRT自动关闭,“如果_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()
返回有用的东西),则运行时将初始化MTA在新创建的主题上。
答案 2 :(得分:22)
通常,正确的做法是调用_beginthread()/_endthread()
(或ex()
变体)。但是,如果您将CRT用作.dll,CRT状态将被正确初始化并销毁,因为在调用{{1}时将使用DllMain
和DLL_THREAD_ATTACH
调用CRT的DLL_THREAD_DETACH
}和CreateThread()
或分别返回。
CRT的ExitThread()
代码可以在VC \ crt \ src \ crtlib.c下的VS安装目录中找到。
答案 3 :(得分:17)
这是_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调用可以正常工作。
答案 4 :(得分:12)
您应该使用_beginthread
或_beginthreadex
来允许C运行时库自己进行线程初始化。只有C / C ++程序员需要知道这一点,因为他们现在应该使用自己的开发环境。
如果您使用_beginthread
,则无需致电CloseHandle
,因为RTL会为您做。这就是您使用_beginthread
时无法等待句柄的原因。如果线程函数立即(快速)退出,那么_beginthread
会导致混淆,因为启动线程会为刚刚启动的线程保留一个无效的线程句柄。
_beginthreadex
句柄可用于等待,但也需要显式调用CloseHandle
。这是使他们安全使用等待的一部分。使其完全万无一失的另一个问题是始终启动线程暂停。检查成功,记录句柄等。恢复线程。这是防止线程在启动线程记录其句柄之前终止的必要条件。
最佳做法是使用_beginthreadex
,在录制句柄后暂停然后恢复,等待句柄正常,必须调用CloseHandle
。
答案 5 :(得分:8)
CreateThread()
used to have memory leaks。 _beginthreadex()
具有与CreateThread()
相同的参数,并且它比_beginthread()
更通用。所以我建议你使用_beginthreadex()
。
答案 6 :(得分:5)
关于您更新的问题:“如果我使用WaitForSingleObject()
,我也会在几个地方看到我无法拨打_beginthread()
,但如果我在_endthread()
拨打WaitForSingleObject()
线程应该不起作用?“
通常,您可以将线程句柄传递给_beginthread()
(或等待对象句柄的其他API)以阻塞,直到线程完成为止。但是_endthread()
创建的线程句柄在调用WaitForSingleObject()
时关闭(可以显式完成,也可以在线程过程返回时由运行时隐式完成)。
问题在{{1}}:
的文档中提到如果在等待仍处于暂挂状态时关闭此句柄,则该函数的行为未定义。
答案 7 :(得分:5)
查看功能签名,CreateThread
几乎与_beginthreadex
相同。
_beginthread
, _beginthreadx
vs 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
);
here上的评论_beginthread
可以使用__cdecl
或__clrcall
调用约定作为起点,而_beginthreadex
可以使用__stdcall
}或__clrcall
作为起点。
我认为人们对CreateThread
内存泄漏所做的任何评论都已有十多年的历史,应该被忽略。
有趣的是,两个_beginthread*
函数实际上都在我的计算机上的CreateThread
内调用了C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src
。
// 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;
}
答案 8 :(得分:3)
beginthreadex
为您HANDLE
和朋友提供了一个WaitForSingleObject
主题。 beginthread
没有。完成后不要忘记CloseHandle()
。真正的答案是使用boost::thread
或很快使用C ++ 09的线程类。
答案 9 :(得分:2)
与_beginthread
相比,_beginthreadex
可以:
OpenThread
一起使用的主题ID。CloseHandle
关闭句柄。 _beginthreadex
非常类似于CreateThread
,但前者是CRT实现,后者是Windows API调用。 CreateThread的文档包含以下建议:
调用C运行时库(CRT)的可执行文件中的线程应使用
_beginthreadex
和_endthreadex
函数进行线程管理,而不是CreateThread
和ExitThread
;这需要使用CRT的多线程版本。如果使用CreateThread
创建的线程调用CRT,则CRT可能会在内存不足的情况下终止进程。
答案 10 :(得分:2)
CreateThread()
曾经是禁忌,因为CRT会错误地初始化/清理。但这已成为历史:现在可以(使用VS2010,可能还有几个版本)在不破坏CRT的情况下调用CreateThread()
。
Here is the official MS confirmation。它说明了一个例外:
实际上,唯一不应该在线程中使用的函数 使用
CreateThread()
创建的是signal()
函数。
但是,从一致的角度来看,我个人更喜欢继续使用_beginthreadex()
。
答案 11 :(得分:2)
CreateThread()
是与语言无关的Windows API调用。它只是创建OS对象 - 线程并将HANDLE返回给该线程。所有Windows应用程序都使用此调用来创建线程。所有语言都避免直接API调用,原因很明显:
1.您不希望您的代码特定于操作系统
2.在调用API之前,您需要做一些内务处理:转换参数和结果,分配临时存储等。
_beginthreadex()
是CreateThread()
的C包装器,它考虑了C特定的。它通过分配特定于线程的存储,使原始单线程C f-ns能够在多线程环境中工作。
如果您不使用CRT,则无法避免直接拨打CreateThread()
。如果使用CRT,则必须使用_beginthreadex()
或某些CRT字符串f-ns在VC2005之前可能无法正常工作。
答案 12 :(得分:1)
CreateThread()
是直接的系统调用。它是在Kernel32.dll
上实现的,很可能,您的应用程序已经因其他原因而被链接。它始终适用于现代Windows系统。
_beginthread()
和_beginthreadex()
是Microsoft C运行时(msvcrt.dll
)中的包装函数。两个调用之间的差异在文档中说明。因此,当Microsoft C Runtime可用时,或者您的应用程序静态链接到它时,它就可用。除非您使用纯Windows API进行编码(我个人经常这样做),否则您可能也会链接到该库。
你的问题是一个连贯的,实际上是一个经常性的问题。与许多API一样,我们必须处理的Windows API中存在重复和模糊的功能。最糟糕的是,文档没有说明问题。我认为_beginthread()
函数族是为了更好地与其他标准C函数集成而创建的,例如errno
的操作。 _beginthread()
因此可以更好地与C运行时集成。
尽管如此,除非你有充分的理由使用_beginthread()
或_beginthreadex()
,否则你应该使用CreateThread()
,主要是因为你可能会在最终的可执行文件中减少一个库依赖(并且MS CRT这有点重要)。您也没有围绕呼叫的包装代码,尽管这种影响可以忽略不计。换句话说,我认为坚持CreateThread()
的主要原因是没有充分的理由使用_beginthreadex()
开头。功能正好或几乎相同。
使用_beginthread()
的一个很好的理由是(因为它似乎是错误的)如果_endthread()
被调用,C ++对象将被正确展开/销毁。
答案 13 :(得分:0)
如果您阅读了调试Jeffrey Richter的Windows应用程序一书,他解释说几乎在所有情况下都必须调用_beginthreadex
而不是调用CreateThread
。 _beginthread
只是_beginthreadex
的简化包装。
_beginthreadex
初始化CreateThread
API不会执行的某些CRT(C RunTime)内部。
如果您使用CreateThread
API而不是使用_begingthreadex
调用CRT函数,则可能会导致意外问题。
答案 14 :(得分:0)
其他答案未能讨论调用包含Win32 API函数的C运行时函数的含义。在考虑DLL加载程序锁定行为时,这很重要。
_beginthread{ex}
是否执行任何特殊的C运行时线程/光纤内存管理,正如其他答案所讨论的那样,它实现在(假设动态链接到C运行时)一个进程可能没有加载的DLL爱好。
从_beginthread*
致电DllMain
是不安全的。我已经通过编写使用Windows“AppInit_DLLs”功能加载的DLL来测试了这一点。调用_beginthreadex (...)
而不是CreateThread (...)
会导致Windows的许多重要部分在启动期间停止运行,因为DllMain
入口点死锁等待释放加载程序锁定以执行某些操作初始化任务。
顺便说一句,这也是为什么 kernel32.dll 有很多重叠的字符串函数,C运行时也会这样做 - 使用来自DllMain
的函数来避免相同的类型情况。
答案 15 :(得分:0)
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<process.h>
UINT __stdcall Staff(PVOID lp){
printf("The Number is %d\n", GetCurrentThreadId());
return 0;
}
INT main(INT argc, PCHAR argv[])
{
const INT Staff_Number = 5;
HANDLE hd[Staff_Number];
for(INT i=0; i < Staff_Number; i++){
hd[i] = (HANDLE)_beginthreadex(NULL, 0, Staff, NULL, 0, NULL);
}
WaitForMultipleObjects(Staff_Number, Staff, TRUE, NULL);
for(INT i=0; i < Staff_Number; i++)
{
CloseHandle(hd[i]);
}
system("pause");
return 0;
}
如果您使用_beginthread而不是_beginthreadex 它将给_beginthread提供太多的错误参数 这是因为_beginthread无法创建具有安全属性的线程 而且我认为_beginthread是不必要的 您可以绝对使用*(_ beginthreadex)和CreateThread
答案 16 :(得分:-2)
两者之间没有任何区别。
关于内存泄漏等的所有评论都是基于非常老的&lt; VS2005版本。 几年前我做过一些压力测试,可以揭穿这个神话。甚至微软也混合了他们的例子中的样式,几乎从不使用_beginthread。