web-dev-qa-db-fra.com

Filetage Windows: _beginthread vs _beginthreadex vs CreateThread C ++

Quel est le meilleur moyen de démarrer un fil de discussion, _beginthread, _beginthreadx Ou CreateThread?

J'essaie de déterminer quels sont les avantages/inconvénients de _beginthread, _beginthreadex Et CreateThread. Toutes ces fonctions renvoient un handle de thread à un thread nouvellement créé. Je sais déjà que CreateThread fournit une petite information supplémentaire en cas d'erreur (elle peut être vérifiée en appelant GetLastError) ... mais quelles sont certaines choses Je devrais envisager quand j'utilise ces fonctions?

Je travaille avec une application Windows, donc la compatibilité entre plates-formes est déjà hors de question.

J'ai parcouru la documentation de msdn et je ne comprends tout simplement pas, par exemple, pourquoi quelqu'un déciderait d'utiliser _beginthread au lieu de CreateThread ou inversement.

À votre santé!

Mise à jour: OK, merci pour toutes les informations. J'ai également lu à quelques endroits que je ne peux pas appeler WaitForSingleObject() si j'ai utilisé _beginthread(), mais si j'ai appelé _endthread() dans le fil de discussion ne devrait-il pas fonctionner? Quel est le problème là-bas?

129
Kiril

CreateThread() est un appel d'API Win32 brut permettant de créer un autre thread de contrôle au niveau du noyau.

_beginthread() & _beginthreadex() sont des appels de bibliothèque d'exécution C qui appellent CreateThread() en arrière-plan. Une fois que CreateThread() est revenu, _beginthread/ex() se charge de la comptabilité supplémentaire pour rendre la bibliothèque d'exécution C utilisable et cohérente dans le nouveau thread.

En C++, vous devriez presque certainement utiliser _beginthreadex() sauf si vous ne voulez pas du tout vous lier à la bibliothèque d'exécution C (alias MSVCRT * .dll/.lib).

94
Drew Hall

Il existe plusieurs différences entre _beginthread() et _beginthreadex(). On a fait en sorte que _beginthreadex() se comporte davantage comme CreateThread() (dans les deux paramètres et leur comportement).

Comme Drew Hall mentionne, si vous utilisez le runtime C/C++, vous devez utiliser _beginthread()/_beginthreadex() au lieu de CreateThread() de sorte que le moteur d'exécution puisse effectuer sa propre initialisation de thread (configuration du stockage local de thread, etc.).

En pratique, cela signifie que CreateThread() ne devrait pratiquement jamais être utilisé directement par votre code.

Les documents MSDN pour _beginthread()/_beginthreadex() contiennent de nombreux détails sur les différences. L'un des plus importants est que, puisque le descripteur de thread pour un thread créé par _beginthread() _ est automatiquement fermé par le CRT lorsque le thread se ferme, "si le thread généré par _beginthread se ferme rapidement, le descripteur renvoyé à l'appelant de _beginthread peut être invalide ou, pire, pointer vers un autre thread".

Voici ce que disent les commentaires de _beginthreadex() dans la source 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).

Mise à jour Jan 2013:

Le tube cathodique de VS 2012 comporte un bit d’initialisation supplémentaire effectué dans _beginthreadex(): si le processus est une "application empaquetée" (si quelque chose d’utile est renvoyé de GetCurrentPackageId()), le moteur d’initialisation initialise le MTA sur le nouveau fil créé.

36
Michael Burr

En général, la bonne chose à faire est d'appeler _beginthread()/_endthread() (ou les variantes ex()). Toutefois, si vous utilisez le tube cathodique en tant que .dll, l'état du tube cathodique sera correctement initialisé et détruit car le DllMain du tube cathodique sera appelé avec DLL_THREAD_ATTACH Et DLL_THREAD_DETACH Lors de l'appel de CreateThread() et ExitThread() ou retour, respectivement.

Le code DllMain du CRT se trouve dans le répertoire d'installation de VS sous VC\crt\src\crtlib.c.

22
MSN

C'est le code au coeur de _beginthreadex (voir 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;
    }

Le reste de _beginthreadex initialise la structure de données par thread pour CRT.

L'avantage d'utiliser _beginthread* est que vos appels CRT à partir du thread fonctionneront correctement.

17
Constantin

Tu devrais utiliser _beginthread ou _beginthreadex pour permettre à la bibliothèque d'exécution C d'effectuer sa propre initialisation du thread. Seuls les programmeurs C/C++ ont besoin de le savoir car ils doivent maintenant connaître les règles d'utilisation de leur propre environnement de développement.

Si tu utilises _beginthread vous n’avez pas besoin d’appeler CloseHandle comme le RTL le fera pour vous. C’est pourquoi vous ne pouvez pas attendre sur la poignée si vous avez utilisé _beginthread. Également _beginthread est source de confusion si la fonction de fil se termine immédiatement (rapidement) car le fil de lancement peut rester avec un handle de fil non valide pour le fil qu'il vient de lancer.

_beginthreadex Les descripteurs peuvent être utilisés pendant l'attente, mais nécessitent également un appel explicite à CloseHandle. Cela fait partie de ce qui les rend sûrs à utiliser avec wait. Un autre problème pour le rendre totalement infaillible est de toujours démarrer le fil suspendu. Vérifiez le succès, le record record, etc. Le fil de reprise. Cela est nécessaire pour empêcher un thread de se terminer avant que le thread de lancement puisse enregistrer son descripteur.

La meilleure pratique consiste à utiliser _beginthreadex, démarrage interrompu puis reprise après enregistrement, attendre que le traitement soit correct, CloseHandle doit être appelé.

12
jarcher7

CreateThread() avait des fuites de mémoire lorsque vous utilisez des fonctions CRT dans votre code. _beginthreadex() a les mêmes paramètres que CreateThread() et il est plus polyvalent que _beginthread(). Je vous recommande donc d'utiliser _beginthreadex().

8
Jaywalker

En regardant les signatures de fonction, CreateThread est presque identique à _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 
);

Les remarques sur ici dire que _beginthread Peut utiliser la convention d'appel __cdecl Ou __clrcall Comme point de départ et que _beginthreadex Peut utiliser __stdcall Ou __clrcall Pour le point de départ.

Je pense que tous les commentaires que les gens ont faits sur les fuites de mémoire dans CreateThread datent de plus de dix ans et devraient probablement être ignorés.

Fait intéressant, les deux fonctions _beginthread* Appellent en fait CreateThread sous le capot, dans C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src sur ma machine.

// 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;
}
5
bobobobo

En ce qui concerne votre question mise à jour: "J'ai également lu à quelques endroits que je ne peux pas appeler WaitForSingleObject() si j'ai utilisé _beginthread(), mais si j'appelle _endthread() dans le fil ne devrait-il pas fonctionner? "

En général, vous pouvez transmettre un descripteur de thread à WaitForSingleObject() (ou à d'autres API qui attendent des descripteurs d'objet) à bloquer jusqu'à la fin du thread. Mais le descripteur de thread créé par _beginthread() est fermé lorsque _endthread() est appelé (ce qui peut être fait explicitement ou est fait implicitement par le moment de l'exécution lorsque la procédure de thread retourne).

Le problème est appelé dans la documentation de WaitForSingleObject():

Si ce descripteur est fermé alors que l'attente est toujours en attente, le comportement de la fonction n'est pas défini.

5
Michael Burr

beginthreadex vous donne un fil HANDLE à utiliser avec WaitForSingleObject et ses amis. beginthread ne le fait pas. N'oubliez pas de CloseHandle() lorsque vous avez terminé. La vraie réponse serait d'utiliser boost::thread ou bientôt la classe de threads de C++ 09.

3
Gert-Jan

Par rapport à _beginthread, avec _beginthreadex vous pouvez:

  1. Spécifiez les attributs de sécurité.
  2. Démarrer un thread en état suspendu.
  3. Vous pouvez obtenir l'identifiant de fil qui peut être utilisé avec OpenThread.
  4. Il est garanti que le descripteur de thread renvoyé est valide si l'appel a abouti. Là, vous devez fermer la poignée avec CloseHandle.
  5. Le descripteur de thread renvoyé peut être utilisé avec les API de synchronisation.

Le _beginthreadex ressemble beaucoup à CreateThread, mais le premier est une implémentation CRT et le dernier un appel API Windows. La documentation de CreateThread contient la recommandation suivante:

Un thread dans un exécutable qui appelle la bibliothèque d'exécution C (CRT) doit utiliser le _beginthreadex et _endthreadex Fonctions pour la gestion des threads plutôt que CreateThread et ExitThread = ; cela nécessite l'utilisation de la version multithread du tube cathodique. Si un thread créé à l'aide de CreateThread appelle le CRT, ce dernier peut mettre fin au processus dans des conditions de mémoire insuffisante.

2
Vishal

CreateThread() est un appel de l'API Windows indépendant du langage. Il crée simplement un objet OS - thread et renvoie HANDLE à ce thread. Toutes les applications Windows utilisent cet appel pour créer des threads. Toutes les langues évitent les appels directs à l'API pour des raisons évidentes: 1. Vous ne souhaitez pas que votre code soit spécifique à l'OS. 2. Vous devez effectuer des tâches internes avant d'appeler une API: convertissez les paramètres et les résultats, allouez le stockage temporaire, etc.

_beginthreadex() est un encapsuleur C autour de CreateThread() qui rend compte de C spécifique. Il permet aux C-ns mono-threadés d'origine de fonctionner dans un environnement multithread en allouant un stockage spécifique à un thread.

Si vous n'utilisez pas CRT, vous ne pouvez pas éviter un appel direct à CreateThread(). Si vous utilisez CRT, vous devez utiliser _beginthreadex() ou une chaîne de chaîne CRT f-ns risque de ne pas fonctionner correctement avant VC2005.

2
SKV

CreateThread() était une fois un non-non, car le CRT ne serait pas correctement initialisé/nettoyé. Mais cela fait maintenant partie de l’histoire: on peut maintenant (avec VS2010 et probablement quelques versions précédentes) appeler CreateThread() sans rompre le tube cathodique.

Voici la confirmation officielle de MS . Il énonce une exception:

En réalité, la seule fonction à ne pas utiliser dans un thread créé avec CreateThread() est la fonction signal().

Cependant, du point de vue de la cohérence, je préfère personnellement continuer à utiliser _beginthreadex().

2
Serge Wautier

CreateThread() est l'appel système direct. Il est implémenté sur Kernel32.dll, Sur lequel, très probablement, votre application sera déjà liée pour d'autres raisons. Il est toujours disponible dans les systèmes Windows modernes.

_beginthread() et _beginthreadex() sont des fonctions d'encapsulation dans le runtime Microsoft C (msvcrt.dll). Les différences entre les deux appels sont indiquées dans la documentation. Il est donc disponible lorsque Microsoft C Runtime est disponible ou si votre application est liée statiquement à celle-ci. Vous lierez probablement aussi avec cette bibliothèque, à moins que vous ne codiez en API Windows pure (comme je le fais souvent personnellement).

Votre question est cohérente et en fait récurrente. Comme beaucoup d’API, l’API Windows contient des fonctionnalités en double et ambiguës. Pire encore, la documentation ne clarifie pas le problème. Je suppose que la famille de fonctions _beginthread() a été créée pour une meilleure intégration avec d'autres fonctionnalités C standard, telles que la manipulation de errno. _beginthread() s'intègre donc mieux au runtime C.

Malgré cela, sauf si vous avez de bonnes raisons d'utiliser _beginthread() ou _beginthreadex(), vous devez utiliser CreateThread(), principalement parce que vous pourriez obtenir une dépendance de bibliothèque en moins dans votre exécutable final. (et pour MS CRT, cela compte un peu). Vous n'avez pas non plus de code d'emballage autour de l'appel, bien que cet effet soit négligeable. En d'autres termes, je pense que la raison principale de rester avec CreateThread() est qu'il n'y a aucune bonne raison d'utiliser _beginthreadex() pour commencer. Les fonctionnalités sont précisément, ou presque, identiques.

Une bonne raison d'utiliser _beginthread()serait (car il semble être faux) que les objets C++ soient correctement déroulés/détruits si _endthread() était appelée.

1
alecov

Si vous lisez le livre Débogage d’une application Windows de Jeffrey Richter, il explique que vous devez presque toujours appeler _beginthreadex au lieu d’appeler CreateThread. _beginthread est juste un wrapper simplifié autour de _beginthreadex.

_beginthreadex initialise certains éléments internes CRT (C RunTime) que l'API CreateThread ne ferait pas.

Une conséquence si vous utilisez l'API CreateThread au lieu d'utiliser _begingthreadex les appels aux fonctions CRT peuvent provoquer des problèmes inattendus.

Consultez cet ancien journal Microsoft de Richter.

0
Ehsan Samani

Les autres réponses n'abordent pas les conséquences de l'appel d'une fonction d'exécution C qui englobe une fonction API Win32. Ceci est important lors de l'examen du comportement de verrouillage du chargeur DLL.

Que ce soit ou non _beginthread{ex} Effectue une gestion spéciale de la mémoire de thread/exécution Fibre C comme le montrent les autres réponses, elle est implémentée dans (en supposant la liaison dynamique à l'exécution C) a DLL = que les processus n’ont peut-être pas encore été chargés.

Il n’est pas prudent d’appeler _beginthread* À partir de DllMain. J'ai testé cela en écrivant un DLL chargé à l'aide de la fonctionnalité Windows "AppInit_DLLs". L'appel de _beginthreadex (...) à la place de CreateThread (...) provoque beaucoup de parties importantes de Windows doit cesser de fonctionner pendant le démarrage en tant que DllMain impasses d’entrée, dans l’attente de la libération du verrou du chargeur, afin d’effectuer certaines tâches d’initialisation.

Incidemment, c’est aussi la raison pour laquelle kernel32.dll possède un grand nombre de fonctions de chaîne qui se chevauchent, comme le fait l’exécution C: utilisez celles de DllMain pour éviter le même genre de situation.

0
Andon M. Coleman