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.