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

时间:2008-12-01 17:20:57

标签: c++ c multithreading winapi

启动帖子的更好方法是_beginthread_beginthreadx还是CreateThread

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

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

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

干杯!

更新: 好的,感谢您提供的所有信息,如果我使用WaitForSingleObject(),我也会在几个地方看到我无法拨打_beginthread(),但如果我在线程中调用_endthread()不应该这样吗?这是什么交易?

17 个答案:

答案 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}时将使用DllMainDLL_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)

在代码中使用任何CRT函数时,

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可以:

  1. 指定安全属性。
  2. 以暂停状态启动线程。
  3. 您可以获取可与OpenThread一起使用的主题ID。
  4. 如果调用的话,返回的线程句柄保证有效 成功的。您需要使用CloseHandle关闭句柄。
  5. 返回的线程句柄可以与同步API一起使用。
  6. _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函数,则可能会导致意外问题。

Check out this old Microsoft Journal From Richter.

答案 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。