Windows 线程处理:_初始线程 vs_初始线程 vs CreateThread C + +

什么是更好的方式开始一个线程,_beginthread_beginthreadxCreateThread

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

我正在使用一个 Windows 应用程序,所以跨平台兼容性已经是不可能的了。

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

干杯!

更新: 好的,感谢所有的信息,我也在一些地方读到,如果我使用 _beginthread(),我就不能调用 WaitForSingleObject(),但是如果我在线程中调用 _endthread(),应该不会起作用吗?怎么回事?

101853 次浏览

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

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

在 C + + 中,几乎可以肯定应该使用 _beginthreadex(),除非根本不链接到 C 运行时库(也就是 MSVCRT *)。Dll/.Lib).

在使用代码中的任何 CRT 函数时,CreateThread()都会出现内存泄漏 _beginthreadex()具有与 CreateThread()相同的参数,并且比 _beginthread()更加通用。所以我建议你使用 _beginthreadex()

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

这是 _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()之间有几个不同之处。_beginthreadex()的行为更像 CreateThread()(在参数和行为方式方面)。

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

在实践中,这意味着 CreateThread()几乎不应该被代码直接使用。

_beginthread()/_beginthreadex()的 MSDN 文档中有很多关于差异的细节——其中一个更重要的是,由于 _beginthread()创建的线程的线程句柄在线程退出时会被 CRT 自动关闭,“如果 _ start 线程生成的线程快速退出,返回给 _ start 线程调用者的句柄可能是无效的,或者更糟糕的是,指向另一个线程”。

以下是 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。

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

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

WaitForSingleObject()的文档中提到了这个问题:

如果在等待仍然挂起时关闭此句柄,则函数的行为是未定义的。

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

如果使用 _beginthread,则不需要像 RTL 那样调用 CloseHandle。这就是为什么你不能等待处理,如果你已经使用 _beginthread。如果线程函数作为启动线程立即(快速)退出,那么 _beginthread也会导致混淆,因为我的启动线程持有它刚刚启动的线程的无效线程句柄。

_beginthreadex句柄可用于等待,但也需要显式调用 CloseHandle。这是使它们可以安全地与 wait 一起使用的部分原因。还有一个使其完全万无一失的问题是始终启动挂起的线程。检查是否成功,记录手柄等。简历的主题。这是必需的,以防止线程在启动线程可以记录其句柄之前终止。

最好的做法是使用 _beginthreadex,开始暂停,然后恢复后记录处理,等待处理是可以的,CloseHandle必须调用。

查看函数签名,CreateThread几乎与 _beginthreadex完全相同。

_beginthread_beginthreadx vs < a href = “ http://msdn.microsoft.com/en-us/library/ms682453(VS. 85) .aspx”rel = “ nofollow noReferrer”> 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*函数实际上都在引擎盖下调用 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;
}

_beginthread相比,使用 _beginthreadex你可以:

  1. 指定安全属性。
  2. 启动处于挂起状态的线程。
  3. 您可以得到线程 id,它可以与 OpenThread一起使用。
  4. 如果调用是有效的,则返回的线程句柄保证是有效的 成功。因为你需要用 CloseHandle关闭手柄。
  5. 返回的线程句柄可以与同步 API 一起使用。

_beginthreadex非常类似于 CreateThread,但前者是 CRT 实现,后者是 Windows API 调用。创建线程的文件载有以下建议:

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

CreateThread()是语言无关的 WindowsAPI 调用。它只是创建 OS 对象线程并将 HANDLE 返回给这个线程。所有 Windows 应用程序都使用此调用来创建线程。所有语言都避免直接 API 调用,原因显而易见: 你不希望你的代码是特定于操作系统的 2. 在调用类似 API 的函数之前,您需要做一些内部管理工作: 转换参数和结果,分配临时存储等。

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

如果你不使用 CRT,你不能避免直接呼叫 CreateThread()。如果您使用 CRT,您必须使用 _beginthreadex()或一些 CRT 字符串 f-ns 可能无法正常工作 VC2005之前。

CreateThread()曾经是一个禁忌,因为 CRT 会被错误地初始化/清理。但是现在这已经是历史了: 人们现在可以(使用 VS2010或许还有几个版本)在不破坏 CRT 的情况下调用 CreateThread()

以下是 MS 的官方确认信息,其中说明了一个例外:

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

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

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

_beginthread()_beginthreadex()是 MicrosoftC 运行时(msvcrt.dll)中的包装函数。文档中说明了这两个调用之间的区别。因此,当 MicrosoftC 运行时可用时,或者当应用程序静态链接到它时,它就可以使用。除非您使用纯 WindowsAPI (我个人经常这样做)编写代码,否则您可能也会链接到这个库。

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

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

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

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

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

如果你读过《从 Jeffrey Richter 那里调试 Windows 应用程序》一书,他解释说几乎在所有的实例中你都必须调用 _beginthreadex而不是 CreateThread_beginthread只是围绕 _beginthreadex的一个简化的包装器。

_beginthreadex初始化 CreateThread API 不会做的某些 CRT (C RunTime)内部结构。

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

看看这本来自里克特的旧微软杂志。

两者之间已经没有区别了。

所有关于内存泄漏等的注释都基于非常老的 < VS2005版本。 我几年前做过一些压力测试可以揭穿这个流言。甚至微软在他们的例子中也混合了这些样式,几乎从来没有使用过 _ start thread。

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

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

DllMain调用 _beginthread*是不安全的。我已经通过编写使用 Windows“ AppInit _ DLL”特性加载的 DLL 对此进行了测试。调用 _beginthreadex (...)而不是 CreateThread (...)会导致 Windows 的许多重要部分在启动期间停止工作,因为 DllMain入口点死锁等待加载程序锁被释放以执行某些初始化任务。

顺便说一句,这也是为什么 内核32.dll有许多重叠的字符串函数,C 运行时也是如此——使用来自 DllMain的函数来避免同样的情况。

你应该试试这个代码

#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;
}

如果你使用 _ _ _ _ 而不是 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 它会给 _ starter 线程提供太多的参数 这是因为 _ startthread 无法创建具有 security 属性的线程 而且我也认为开始线程是不必要的 您完全可以使用 * (_  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _