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


133

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

我试图确定什么是优势/劣势_beginthread_beginthreadexCreateThread。所有这些函数都将线程句柄返回到新创建的线程,我已经知道,当发生错误时(可以通过调用来检查GetLastError),CreateThread提供了一些额外的信息。米使用这些功能?

我正在使用Windows应用程序,因此跨平台兼容性已经成为不可能。

我已经阅读了msdn文档,例如,我什至不明白为什么有人会决定使用_beginthread而不是CreateThread,反之亦然。

干杯!

更新:OK,所有的信息,谢谢,我也看到了在几个地方,我不能打电话WaitForSingleObject(),如果我使用的_beginthread(),但如果我呼吁_endthread()在线程不应该工作?那有什么事


2
这是我从Eli Bendersky网站上的链接中找到的_beginthreadex()对C / C ++程序员的作用的分析。这是有关是否使用CreateThread()的问答。 microsoft.com/msj/0799/win32/win320799.aspx
Richard Chambers

Answers:


96

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

_beginthread()_beginthreadex()CreateThread()在后台调用的C运行时库调用。一旦CreateThread()返回,请_beginthread/ex()多做一些记账工作,以使C运行时库在新线程中可用且一致。

在C ++中,几乎应该肯定会使用,_beginthreadex()除非根本不会链接到C运行时库(aka MSVCRT * .dll / .lib)。


39
这不再像以前那样真实。CRT将在由CreateThread()创建的线程中正常运行,但signal()函数除外。使用CRT的CreateThread()创建的每个线程都会有少量的内存泄漏(〜80字节),但是它将正常运行。看到更多信息:support.microsoft.com/default.aspx/kb/104641
John Dibling 09年

1
@John:实际上,该错误仅适用于MSVC ++ 6.0
bobobobo 2010年

5
@bobobobo:好问题。我只能推测,MS最初打算将_begin例程作为内部调用,并且CreateThread应该将其作为每个人都会调用的API函数。另一个可能的解释是,MS拥有悠久而光荣的历史,它忽略了标准并为命名事物做出了非常糟糕的决定。
John Dibling 2010年

15
这些_begin功能以下划线开头,因为 Microsoft开始更加严格地遵循该标准。在C运行时中,带下划线的名称是为实现保留的(实现可以将它们记录下来,以供最终用户使用)。beginthreadex()是允许用户使用的名称。如果C运行时使用了它,则它可能与最终用户的符号冲突,该符号表示用户具有可以使用的合法权利。请注意,Win32 API不是C运行时的一部分,它们使用用户的名称空间。
Michael Burr

2
@Lothar:有 Win32 API的调用之间的差异CreateThread和CRT调用_beginthread/ex,并在一个线程调用CRT时,应始终创建_beginthread/ex。如果没有的话,可能不再有内存泄漏。但是CreateThread,例如,在调用时,您肯定不会正确初始化浮点环境。还有更多的内容“如果使用CreateThread创建的线程调用CRT,则CRT可能会在内存不足的情况下终止进程。”
IInspectable'2

37

_beginthread()和之间有一些区别_beginthreadex()。 使其表现_beginthreadex()得更像CreateThread()(在两个参数及其行为方面)。

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

实际上,这意味着CreateThread()您的代码几乎绝不应直接使用它。

有关_beginthread()/ 的MSDN文档中_beginthreadex()有很多差异方面的详细信息-更为重要的一个是,由于_beginthread()CRT会在线程退出时自动关闭由创建的线程的线程句柄,因此“如果_beginthread生成的线程退出了很快,返回给_beginthread调用者的句柄可能无效,或者更糟的是,指向另一个线程”。

以下是_beginthreadex()CRT来源中的评论:

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。


3
这里当的CreateThread()是必要的适当的时间,但老实说,你真的要离开自己的方式做出来。我们正在谈论的是完全没有可移植的东西,而是专门编写了WIN32 API DLL或App。不包括C运行时调用。甚至STL的使用也受到限制,因为您必须提供自定义分配器才能使用WIN32内存管理功能。使用Developer Studio进行此设置本身就是一项工作,但对于只有WINDOWS最小占用空间的lib来说,它是可以完成的。但是,是的,除了极少数人之外,几乎没有其他人会流血。
WhozCraig 2012年

1
@WhozCraig:省略CRT时会有更严格的限制。最突出的是:不支持64位整数,不支持浮点,并且-最主要的是-没有异常处理。这实际上意味着完全没有异常处理。甚至没有SEH例外。这尤其难以弥补,CreateThread被称为“正确事物”的机会越来越少。
IInspectable 2014年

@MichaelBurr:您可能想更新VC ++ 2015的答案
user541686

@Mehrdad:您特别值得提及哪些变化?
IInspectable

我发现DisableThreadLibraryCalls对使用CreateThread创建的线程没有影响,但是确实禁用了用_beginthread或_beginthreadex创建的线程。
SPlatten

23

通常,正确的做法是调用_beginthread()/_endthread()(或ex()变体)。但是,如果您使用CRT作为一个.dll,在CRT状态将被正确的初始化和销毁CRT的DllMain将被用DLL_THREAD_ATTACHDLL_THREAD_DETACH打电话时CreateThread()ExitThread()或分别返回。

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


很好的起点。进行一点调试,就可以显示__CRTDLL_INIT被调用,即使是对于静态链接的CRT也是如此。调用堆栈的init是从_LdrpCallInitRoutine @ 16()调用的,我不确定到底是通过什么机制实现的。这意味着使用最新的CRT,除信号处理外,所有初始化/取消初始化均已正确完成,该处理仍在从beginthread调用的_threadstartex帮助程序函数中完成,但从CreateThread调用的函数未完成。也许您可以将其添加到答案中,我将颁发赏金?
Suma 2012年

获得赏金,因为这似乎最有帮助。不过,答案也许值得更新。如果您做不到,我可能会在几天之内重新进行审查。
Suma 2012年

1
@MSN:请注意,如果您再次链接静态CRT 调用了DisableThreadLibraryCalls,它会禁用DLL_THREAD_DETACH的调用,但DLL中的CreateThread仍然很糟糕。然后,您将获得内存泄漏。这是记录在这里我知识库文章:support.microsoft.com/kb/555563/en-us
约亨Kalmbach

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调用将正常工作。


12

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

如果使用_beginthread,则不需要调用,CloseHandle因为RTL会为您服务。这就是为什么如果您使用过,您将无法等待手柄_beginthread_beginthread如果线程函数立即(迅速)退出,这也会引起混乱,因为启动线程可能会在刚刚启动的线程上持有无效的线程句柄。

_beginthreadex句柄可用于等待,但也需要显式调用CloseHandle。这是使它们在等待时可以安全使用的部分原因。使其完全安全的另一个问题是始终将线程挂起。检查是否成功,记录句柄等。恢复线程。为了防止线程在启动线程可以记录其句柄之前终止,这是必需的。

最佳做法是使用_beginthreadex,开始暂停后再录制句柄后恢复,等待句柄可以,CloseHandle必须调用。


8

CreateThread()当您在代码中使用任何CRT函数时,通常会导致内存泄漏_beginthreadex()具有与相同的参数,CreateThread()并且比更具通用性_beginthread()。所以我建议您使用_beginthreadex()


2
1999年的文章,可能因为已经固定
bobobobo

1
2005年以来的这篇文章仍然确认存在问题。
Jaywalker 2011年

2
是的,它仅适用于MSVC ++ 6.0 Service Pack 5和更早版本。(请参见“适用于”展开式下拉菜单)。如果您使用的是VC7或更高版本,那么今天这不是问题。
bobobobo

1
如果您再次链接静态CRT,这仍然是一个问题!如果您在静态链接的DLL中调用DisableThreadLibraryCalls,这仍然是一个问题。请参阅我的知识库文章:support.microsoft.com/kb/555563/en-us
Jochen Kalmbach 2014年

2
你歪曲的信息:CreateThread没有没有过泄漏内存。当从尚未正确初始化的线程中调用CRT时,它将执行此操作。
IInspectable 2014年

6

关于您更新的问题:“ WaitForSingleObject()如果使用过_beginthread(),我也读过一些我无法调用的地方,但是如果我_endthread()在线程中调用,那行不通吗?”

通常,您可以将线程句柄传递给WaitForSingleObject()(或其他等待对象句柄的API)以阻塞直到线程完成。但是,由创建的线程句柄在调用_beginthread()时是关闭的_endthread()(可以显式完成,也可以在线程过程返回时在运行时隐式完成)。

该问题在文档中针对WaitForSingleObject()

如果在等待仍未完成的情况下关闭此句柄,则该函数的行为是不确定的。


5

查看函数签名CreateThread几乎与相同_beginthreadex

_beginthread_beginthreadx vsCreateThread

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*功能实际上都是在我的机器内部调用CreateThreadC:\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;
}

2
注释一下,为什么不应该调用CreateThread并混入该线程上的CRT调用(绝对不存在十年,并且绝对不应忽略)“如果使用CreateThread创建的线程调用CRT,则CRT可能会终止该进程。低内存条件。”
IInspectable '16

3

beginthreadex给您提供了一个HANDLEWaitForSingleObject和朋友使用的线程。beginthread不。完成后别忘CloseHandle()了。真正的答案将是使用boost::threadC ++ 09的线程类,还是很快使用。


msdn描述说:“如果成功,这些函数中的每一个都会返回新创建线程的句柄;” 指的是_beginthread()和_beginthreadex()...
Kiril

@Kiril:但是文档接着说_beginthread为您关闭了句柄,这意味着如果线程快速退出,您将无法使用它……
Roger Lipscombe

2

与相比_beginthread_beginthreadex您可以:

  1. 指定安全属性。
  2. 以挂起状态启动线程。
  3. 您可以获取可与一起使用的线程ID OpenThread
  4. 如果调用成功,则保证返回的线程句柄有效。在那里您需要用闭合手柄CloseHandle
  5. 返回的线程句柄可以与同步API一起使用。

两者极为_beginthreadex相似CreateThread,但前者是CRT实现,而后者是Windows API调用。CreateThread的文档包含以下建议:

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


根据API规范,项目符号3-5不是唯一的_beginthreadex。您可以uintptr_t将这两个函数的返回值强制转换为HANDLE
安东·科尔曼

是的,您在理论上是正确的。实际上,不同之处在于_beginthread在退出时会关闭手柄。因此,除非并且除非使用另一种方式来同步和复制该句柄,否则您不能可靠地将句柄与同步API一起使用或获取线程ID。但是,这就是_beginthreadex为您做的。
维沙尔

2

CreateThread()曾经是一个禁忌,因为CRT将被错误地初始化/清理。但这已经成为历史:现在可以调用(使用VS2010,可能还提供了一些版本)CreateThread()而无需中断CRT。

这是官方的MS确认。它指出一个例外:

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

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


尽管我认为这是对的,但您能否提供一些权威证据-通过链接到MS Documentation还是通过分析CRT _beginthreadex / _endthreadex源?
Suma 2012年

@Suma,我想我在您输入评论时添加了MS链接;-)
Serge Wautier

您链接到的文档似乎无法确认:“但是,根据调用的CRT函数的不同,线程终止时可能会发生少量内存泄漏。”。这意味着它不再是一个普遍的大禁忌,但是如果您经常创建线程并在其中使用这些函数,那么它仍然是一个禁忌。但是,该文档来自2005年,因此无法解决此问题的最新状态。
Suma 2012年

1
嗯...虽然可能取决于用例,但函数会导致内存泄漏,无论大小如何,我都认为不行...-特别是如果存在不泄漏的情况!
2012年

“现在可以在不破坏CRT的情况下调用CreateThread()了。” -不幸的是,这不是事实,而且从未如此。来自CreateThread“调用C运行时库(CRT)的可执行文件中的线程应使用_beginthreadex和_endthreadex函数进行线程管理[...]如果使用CreateThread创建的线程调用CRT,则CRT可能终止在低内存条件下进行处理。”
IInspectable '16

2

CreateThread()是与语言无关的Windows API调用。它只是创建OS对象-线程,并向该线程返回HANDLE。所有Windows应用程序都使用此调用来创建线程。由于明显的原因,所有语言都避免直接调用API:1.您不希望代码特定于OS。2.在调用类似API之前,您需要做一些整理工作:转换参数和结果,分配临时存储空间等。

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

如果您不使用CRT,则无法避免直接致电CreateThread()。如果使用CRT,则必须使用_beginthreadex()VC2005之前的版本,否则某些CRT字符串f-ns可能无法正常工作。


1

CreateThread()是直接的系统调用。它是在Kernel32.dll其上实现的,很可能是由于其他原因而已将您的应用程序链接到该应用程序。它在现代Windows系统中始终可用。

_beginthread()并且_beginthreadex()是Microsoft C运行时(msvcrt.dll)。文档中说明了两次调用之间的区别。因此,当Microsoft C运行时可用,或者您的应用程序与之静态链接时,它就可用。除非您使用纯Windows API进行编码(就像我个人经常做的那样),否则您也可能会链接到该库。

您的问题是一个连贯的问题,实际上是一个反复出现的问题。与许多API一样,我们必须处理Windows API中存在重复和模棱两可的功能。最糟糕的是,文档并未阐明问题。我想_beginthread()创建函数族是为了更好地与其他标准C功能集成,例如的操纵errno_beginthread()因此可以更好地与C运行时集成。

尽管如此,除非您有充分的理由使用_beginthread()或,否则_beginthreadex()应使用CreateThread(),主要是因为在最终的可执行文件中,库依赖关系可能会减少一些(对于MS CRT,这确实有点影响)。您也没有环绕该调用的包装代码,尽管这种影响可以忽略不计。换句话说,我相信坚持的主要原因CreateThread()是没有充分的理由_beginthreadex()开始使用。功能完全或几乎相同。

使用的一个很好的理由_beginthread() (似乎是错误的),如果_endthread()调用了C ++对象,则可以正确展开/销毁它们。


有没有暧昧的函数调用在所有CreateThread是创建线程的Windows API调用。如果您使用的是CRT(因为您使用C或C ++进行编程),则应使用CRT的_beginthread[ex]调用(CreateThread除了执行必要的CRT初始化之外,还可以使用这些调用来创建线程)。_beginthread与ex变量之间最重要的区别:前者保留对本机线程句柄的所有权,而后者将所有权传递给调用者。
IInspectable '16

鸡蛋里挑骨头:msvcrt.dll不是 C运行库DLL!请参阅blogs.msdn.microsoft.com/oldnewthing/20140411-00/?p=1273
Govind Parmar,

0

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

不管是否_beginthread{ex}有其他答案讨论的任何特殊的C运行时线程/光纤内存管理,它都在(假设动态链接到C运行时)一个可能尚未加载的DLL中实现。

_beginthread*从打来的电话并不安全DllMain。我已经通过编写使用Windows“ AppInit_DLLs”功能加载的DLL进行了测试。调用_beginthreadex (...)而不是CreateThread (...)导致Windows的许多重要部分在启动期间停止运行,因为DllMain入口点死锁等待释放加载程序锁以执行某些初始化任务。

顺便说一句,这也是为什么kernel32.dll具有许多C运行时也重叠的字符串函数的原因-使用这些函数DllMain以避免相同的情况。


0

如果您读了《从Jeffrey Richter调试Windows应用程序》一书,他解释说几乎在所有情况下都必须调用_beginthreadex而不是调用CreateThread_beginthread只是一个简化的包装器_beginthreadex

_beginthreadex初始化CreateThreadAPI不会执行的某些CRT(C运行时)内部。

如果您使用CreateThreadAPI而不是使用_begingthreadex对CRT函数的调用,可能会导致意外的问题。

查阅这份来自Richter的旧Microsoft Journal。


-2

两者之间不再有区别。

关于内存泄漏等的所有注释均基于非常旧的<VS2005版本。几年前,我已经进行了一些压力测试,并且可以揭穿这个神话。甚至Microsoft都在示例中混合了样式,几乎从未使用_beginthread。


CreateThread“如果使用CreateThread创建的线程调用CRT,则CRT可能会在内存不足的情况下终止进程。”
IInspectable '16

基于这个陈述,“我要求使用CRT的多线程版本”,我认为这是文档浪费,因为现在已经没有多线程crt版本了很多年。
Lothar

“没有多线程CRT版本了” -的MSDN声称“[t]把单线程CRT不再可用”。你们俩不可能都是对的。我也将在这里使用MSDN。
IInspectable

这是一个错字,当然,我的意思是单线程已消失而多线程已成为标准,而不再是使用或不使用线程之间的区别。
Lothar'7

这真的很奇怪。您现在使用的语句无疑是正确的(“要求使用CRT的多线程版本”)声称该语句以及文档的其余部分很可能是错误的?那肯定听起来不对。
IInspectable
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.