Потоки Windows: _beginthread против _beginthreadex против CreateThread C++
Какой лучший способ начать поток, _beginthread
, _beginthreadx
или же CreateThread
?
Я пытаюсь определить, каковы преимущества / недостатки _beginthread
, _beginthreadex
а также CreateThread
, Все эти функции возвращают дескриптор потока во вновь созданный поток, я уже знаю, что CreateThread предоставляет немного дополнительной информации при возникновении ошибки (это можно проверить, вызвав GetLastError
)... но что я должен учитывать при использовании этих функций?
Я работаю с приложением Windows, поэтому о кросс-платформенной совместимости уже не может быть и речи.
Я просмотрел документацию по msdn и просто не могу понять, например, почему кто-то решил использовать _beginthread вместо CreateThread или наоборот.
Ура!
Обновление: хорошо, спасибо за всю информацию, я также прочитал в нескольких местах, которые я не могу назвать WaitForSingleObject()
если бы я использовал _beginthread()
, но если я позвоню _endthread()
в теме не должно работать? Что там за сделка?
16 ответов
CreateThread()
это необработанный вызов Win32 API для создания другого потока управления на уровне ядра.
_beginthread()
& _beginthreadex()
являются вызовами библиотеки времени выполнения C, которые вызывают CreateThread()
за кулисами. однажды CreateThread()
вернулся, _beginthread/ex()
заботится о дополнительном ведении бухгалтерии, чтобы сделать библиотеку времени выполнения C пригодной для использования и согласованной в новом потоке.
В C++ вы почти наверняка должны использовать _beginthreadex()
если вы вообще не будете ссылаться на библиотеку времени выполнения C (она же MSVCRT*.dll/.lib).
Есть несколько различий между _beginthread()
а также _beginthreadex()
, _beginthreadex()
было сделано, чтобы действовать как CreateThread()
(по обоим параметрам и как он себя ведет).
Как упоминает Дрю Холл, если вы используете среду выполнения C/C++, вы должны использовать _beginthread()
/_beginthreadex()
вместо CreateThread()
чтобы у среды выполнения была возможность выполнить собственную инициализацию потока (настройка локального хранилища потока и т. д.).
На практике это означает, что CreateThread()
никогда не должен использоваться непосредственно вашим кодом.
Документы MSDN для _beginthread()
/_beginthreadex()
иметь немного деталей о различиях - одна из наиболее важных состоит в том, что, поскольку дескриптор потока для потока создан _beginthread()
CRT автоматически закрывается при выходе из потока: "если поток, сгенерированный _beginthread, быстро завершается, дескриптор, возвращаемый вызывающей стороне _beginthread, может быть недействительным или, что еще хуже, указывать на другой поток".
Вот то, что комментарии для _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:
CRT для VS 2012 имеет дополнительный бит инициализации, выполненный в _beginthreadex()
: если процесс представляет собой "упакованное приложение" (если из GetCurrentPackageId()
) среда выполнения инициализирует MTA во вновь созданном потоке.
В общем, правильное решение - позвонить _beginthread()/_endthread()
(или ex()
варианты). Однако, если вы используете CRT в качестве DLL, состояние CRT будет правильно инициализировано и уничтожено как CRT DllMain
будет вызван с DLL_THREAD_ATTACH
а также DLL_THREAD_DETACH
при звонке CreateThread()
а также ExitThread()
или вернувшись, соответственно.
DllMain
код для CRT можно найти в каталоге установки для VS в VC\crt\src\crtlib.c.
Это код в основе _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
, Это часть того, что делает их безопасными для использования с ожиданием. Есть и другая проблема, которая делает его полностью защищенным от неправильного обращения - всегда начинать приостановленную резьбу. Проверка на успех, запись дескриптора и т.д. Это необходимо для предотвращения завершения потока до того, как запускающий поток сможет записать свой дескриптор.
Лучшая практика заключается в использовании _beginthreadex
, начать приостановлено, затем возобновить после записи дескриптора, ждать на дескрипторе в порядке, CloseHandle
должен быть назван.
CreateThread()
имел место утечки памяти при использовании любых функций CRT в вашем коде. _beginthreadex()
имеет те же параметры, что и CreateThread()
и это более универсально, чем _beginthread()
, Поэтому я рекомендую вам использовать _beginthreadex()
,
Относительно вашего обновленного вопроса: "Я также читал в нескольких местах, которые я не могу назвать WaitForSingleObject()
если бы я использовал _beginthread()
, но если я позвоню _endthread()
в теме не должно сработать?"
В общем, вы можете передать дескриптор потока WaitForSingleObject()
(или другие API-интерфейсы, ожидающие дескрипторов объектов) для блокировки до завершения потока. Но дескриптор потока создан _beginthread()
закрыто, когда _endthread()
вызывается (что может быть сделано явно или неявно во время выполнения, когда процедура потока возвращается).
Проблема вызывается в документации для WaitForSingleObject()
:
Если этот дескриптор закрыт, пока ожидание еще не завершено, поведение функции не определено.
Глядя на подписи функции, 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
старше 10 лет и, вероятно, следует игнорировать.
Интересно, что оба _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;
}
CreateThread()
когда-то было нет-нет, потому что CRT будет неправильно инициализировать / очистить. Но теперь это история: теперь можно (используя VS2010 и, возможно, несколько версий назад) вызвать CreateThread()
не нарушая ЭЛТ.
Вот официальное подтверждение MS. Это заявляет одно исключение:
На самом деле, единственная функция, которая не должна использоваться в потоке, созданном с
CreateThread()
этоsignal()
функция.
Тем не менее, с точки зрения консистенции, я лично предпочитаю продолжать использовать _beginthreadex()
,
beginthreadex
дает вам нить HANDLE
для использования в WaitForSingleObject
и друзья. beginthread
не делает. Не забудь CloseHandle()
когда вы сделали. Реальный ответ будет использовать boost::thread
или скоро класс потока C++09.
По сравнению с _beginthread
, с _beginthreadex
вы можете:
- Укажите атрибуты безопасности.
- Запустить поток в приостановленном состоянии.
- Вы можете получить идентификатор потока, который можно использовать с
OpenThread
, - Возвращенный дескриптор потока гарантированно будет действительным, если вызов был успешным. Там для вас нужно закрыть ручку с
CloseHandle
, - Возвращенный дескриптор потока может использоваться с API-интерфейсами синхронизации.
_beginthreadex
близко напоминает CreateThread
, но первая является реализацией CRT, а вторая - вызовом API Windows. Документация для CreateThread содержит следующую рекомендацию:
Поток в исполняемом файле, который вызывает библиотеку времени выполнения C (CRT), должен использовать
_beginthreadex
а также_endthreadex
функции для управления потоками, а неCreateThread
а такжеExitThread
; это требует использования многопоточной версии CRT. Если поток создан с использованиемCreateThread
вызывает CRT, CRT может завершить процесс в условиях нехватки памяти.
CreateThread()
это вызов Windows API, который не зависит от языка. Он просто создает объект OS - поток и возвращает HANDLE этому потоку. Все приложения Windows используют этот вызов для создания потоков. Все языки избегают прямого вызова API по очевидным причинам:
1. Вы не хотите, чтобы ваш код был специфичен для ОС 2. Вам необходимо провести некоторую домашнюю работу перед вызовом API-подобного: преобразовать параметры и результаты, выделить временное хранилище и т. Д.
_beginthreadex()
это C обертка вокруг CreateThread()
что составляет специфический C. Это позволяет исходным однопотоковым C-ns работать в многопоточной среде, выделяя специфичное для потока хранилище.
Если вы не используете CRT, вы не можете избежать прямого звонка CreateThread()
, Если вы используете CRT, вы должны использовать _beginthreadex()
или некоторые строки CRT могут не работать должным образом до VC2005.
CreateThread()
это прямой системный вызов. Это реализовано на Kernel32.dll
с которым, скорее всего, ваше приложение уже будет связано по другим причинам. Он всегда доступен в современных системах Windows.
_beginthread()
а также _beginthreadex()
такое функции-оболочки в Microsoft C Runtime (msvcrt.dll
). Различия между двумя вызовами указаны в документации. Таким образом, он доступен, когда доступна среда выполнения Microsoft C или если ваше приложение статически связано с ней. Скорее всего, вы тоже будете ссылаться на эту библиотеку, если только вы не программируете на чистом Windows API (как я часто это делаю).
Ваш вопрос является последовательным и фактически повторяющимся. Как и многие API, в Windows API есть двойная и неоднозначная функциональность, с которой нам приходится иметь дело. Хуже всего то, что документация не проясняет проблему. Я полагаю, что _beginthread()
семейство функций было создано для лучшей интеграции с другими стандартными функциями C, такими как манипулирование errno
, _beginthread()
таким образом, лучше интегрируется со средой выполнения C.
Несмотря на это, если у вас нет веских причин для использования _beginthread()
или же _beginthreadex()
, вы должны использовать CreateThread()
Главным образом потому, что в конечном исполняемом файле вы можете получить на одну библиотечную зависимость меньше (а для MS CRT это имеет значение). У вас также нет кода переноса вызова, хотя этот эффект незначителен. Другими словами, я считаю, что главная причина придерживаться CreateThread()
в том, что нет веских причин использовать _beginthreadex()
начать с. Функциональные возможности точно или почти одинаковы.
Одна хорошая причина для использования _beginthread()
было бы (как это кажется ложным), что объекты C++ были бы должным образом развернуты / уничтожены, если _endthread()
назывался.
Если вы читаете в ней книгу "Отладка приложения Windows от Джеффри Рихтера", он объясняет, что почти во всех случаях вы должны вызывать _beginthreadex
вместо звонка CreateThread
, _beginthread
это просто упрощенная оболочка вокруг _beginthreadex
,
_beginthreadex
инициализирует определенные внутренние элементы CRT (C RunTime), которые CreateThread
API не будет делать.
Следствие, если вы используете CreateThread
API вместо использования _begingthreadex
вызовы функций CRT могут вызвать непредвиденные проблемы.
Другие ответы не обсуждают последствия вызова функции времени выполнения C, которая оборачивает функцию Win32 API. Это важно при рассмотрении поведения блокировки загрузчика DLL.
Так или иначе _beginthread{ex}
выполняет какое-либо специальное управление потоками / волоконной памятью C Runtime, как обсуждаются в других ответах, оно реализуется (при условии динамического связывания с C-средой выполнения) DLL, которую процессы, возможно, еще не загрузили.
Не безопасно звонить _beginthread*
от DllMain
, Я проверил это, написав DLL, загруженную с помощью функции Windows "AppInit_DLLs". призвание _beginthreadex (...)
вместо CreateThread (...)
вызывает множество важных частей Windows, чтобы перестать функционировать во время загрузки, как DllMain
блокировка точки входа, ожидающая снятия блокировки загрузчика для выполнения определенных задач инициализации.
Кстати, именно поэтому kernel32.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;
}
если вы используете _beginthread вместо _beginthreadex, это приведет к ошибке слишком много аргументов для _beginthread, это потому, что _beginthread не может создать поток с атрибутом безопасности, а также я думаю, что _beginthread не нужен, вы можете абсолютно использовать *(_beginthreadex) и CreateThread
Больше нет никакой разницы между обоими.
Все комментарии об утечках памяти и т. Д. Основаны на очень старых версиях