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