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?
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).
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éé.
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.
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.
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é.
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()
.
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;
}
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.
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.
Par rapport à _beginthread
, avec _beginthreadex
vous pouvez:
OpenThread
.CloseHandle
.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 queCreateThread
etExitThread
= ; cela nécessite l'utilisation de la version multithread du tube cathodique. Si un thread créé à l'aide deCreateThread
appelle le CRT, ce dernier peut mettre fin au processus dans des conditions de mémoire insuffisante.
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.
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 fonctionsignal()
.
Cependant, du point de vue de la cohérence, je préfère personnellement continuer à utiliser _beginthreadex()
.
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.
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.
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.