Supposons que je démarre un std::thread
et puis detach()
il, donc le thread continue à s'exécuter même si celui std::thread
qui l'a représenté une fois, sort de la portée.
Supposons en outre que le programme ne dispose pas d'un protocole fiable pour joindre le thread détaché 1 , de sorte que le thread détaché s'exécute toujours lorsqu'il se main()
termine.
Je ne trouve rien dans la norme (plus précisément, dans le projet N3797 C ++ 14), qui décrit ce qui devrait se passer, ni 1.10 ni 30.3 ne contiennent un libellé pertinent.
1 Une autre question, probablement équivalente, est la suivante: "un thread détaché peut-il être joint à nouveau", car quel que soit le protocole que vous inventez pour le rejoindre, la partie de signalisation devrait être Décider de mettre le thread en veille pendant une heure juste après que la signalisation a été effectuée sans aucun moyen pour l'extrémité de réception de détecter de manière fiable que le thread est effectivement terminé.
Si le fait de manquer de main()
threads détachés en cours d'exécution n'est pas un comportement défini, alors toute utilisation de std::thread::detach()
est un comportement indéfini à moins que le thread principal ne se termine jamais 2 .
Ainsi, manquer de main()
threads détachés en cours d'exécution doit avoir des effets définis . La question est: où (dans le standard C ++ , pas POSIX, pas de documentation OS, ...) sont définis ces effets.
2 Un fil détaché ne peut pas être joint (au sens de std::thread::join()
). Vous pouvez attendre les résultats des threads détachés (par exemple via un futur from std::packaged_task
, ou par un sémaphore de comptage ou un drapeau et une variable de condition), mais cela ne garantit pas que le thread a fini de s'exécuter . En effet, à moins que vous ne mettiez la partie signalisation dans le destructeur du premier objet automatique du thread, il y aura , en général, du code (destructeurs) qui s'exécutera après le code de signalisation. Si le système d'exploitation planifie le thread principal pour qu'il consomme le résultat et se termine avant que le thread détaché ait fini d'exécuter lesdits destructeurs, qu'est-ce que ^ Sera défini pour se produire?
std::exit
ou la sortie demain
est suffisant, mais pas nécessaire, pour satisfaire ces exigences." (le paragraphe entier peut être pertinent) Voir aussi [support.start.term] / 8 (std::exit
est appelé aumain
retour)Réponses:
La réponse à la question initiale "qu'arrive-t-il à un thread détaché à la
main()
sortie" est:Il continue de fonctionner (car le standard ne dit pas qu'il est arrêté), et c'est bien défini, tant qu'il ne touche ni les variables (automatic | thread_local) des autres threads ni les objets statiques.
Cela semble être autorisé à autoriser les gestionnaires de threads en tant qu'objets statiques (la note dans [basic.start.term] / 4 en dit long, grâce à @dyp pour le pointeur).
Des problèmes surviennent lorsque la destruction des objets statiques est terminée, car alors l'exécution entre dans un régime où seul le code autorisé dans les gestionnaires de signaux peut s'exécuter ( [basic.start.term] / 1, 1ère phrase ). De la bibliothèque standard C ++, c'est seulement la
<atomic>
bibliothèque ( [support.runtime] / 9, 2ème phrase ). En particulier, cela - en général - exclutcondition_variable
(il est défini par l'implémentation si cela est sauvegardé pour être utilisé dans un gestionnaire de signaux, car il n'en fait pas partie<atomic>
).Sauf si vous avez déroulé votre pile à ce stade, il est difficile de voir comment éviter un comportement indéfini.
La réponse à la deuxième question «les threads détachés peuvent-ils être rejoints» est:
Oui, avec la
*_at_thread_exit
famille de fonctions (notify_all_at_thread_exit()
,std::promise::set_value_at_thread_exit()
...).Comme noté dans la note de bas de page [2] de la question, signaler une variable de condition ou un sémaphore ou un compteur atomique n'est pas suffisant pour rejoindre un thread détaché (dans le sens de s'assurer que la fin de son exécution s'est produite-avant la réception de ladite signalisation par un thread en attente), car, en général, il y aura plus de code exécuté après par exemple une
notify_all()
d'une variable de condition, en particulier les destructeurs d'objets automatiques et thread-locaux.Exécuter la signalisation comme la dernière chose que fait le thread ( après que les destructeurs d'objets automatiques et locaux du thread se soient produits ) est ce pour quoi la
_at_thread_exit
famille de fonctions a été conçue.Ainsi, afin d'éviter un comportement indéfini en l'absence de garanties d'implémentation supérieures à ce que la norme exige, vous devez (manuellement) rejoindre un thread détaché avec une
_at_thread_exit
fonction faisant la signalisation ou faire en sorte que le thread détaché exécute uniquement du code qui serait sûr pour un gestionnaire de signaux, aussi.la source
exit
fini de détruire des objets statiques, d'exécuter desatexit
gestionnaires, de purger des flux, etc., il retourne le contrôle à l'environnement hôte, c'est-à-dire que le processus se termine . Si un thread détaché est toujours en cours d'exécution (et a en quelque sorte évité un comportement indéfini en ne touchant rien en dehors de son propre thread), il disparaît alors dans une bouffée de fumée lorsque le processus se termine.main
appelspthread_exit
au lieu de renvoyer ou d'appeler,exit
le processus attendra la fin des threads détachés, puis l'appel uneexit
fois le dernier terminé.Détacher des threads
Selon
std::thread::detach
:De
pthread_detach
:La dissociation des threads sert principalement à économiser des ressources, au cas où l'application n'aurait pas besoin d'attendre la fin d'un thread (par exemple, les démons, qui doivent s'exécuter jusqu'à la fin du processus):
std::thread
objet sortir de sa portée sans le rejoindre, ce qui conduit normalement à un appel à lastd::terminate()
destruction.Tuer des fils
Le comportement à la fin du processus est le même que celui du thread principal, qui pourrait au moins capter certains signaux. Que d'autres threads puissent ou non gérer les signaux n'est pas si important, car on pourrait rejoindre ou terminer d'autres threads dans l'appel du gestionnaire de signaux du thread principal. (Question connexe )
Comme déjà indiqué, tout thread, qu'il soit détaché ou non, mourra avec son processus sur la plupart des systèmes d'exploitation . Le processus lui-même peut être terminé en élevant un signal, en appelant
exit()
ou en revenant de la fonction principale. Cependant, C ++ 11 ne peut pas et n'essaye pas de définir le comportement exact du système d'exploitation sous-jacent, alors que les développeurs d'une machine virtuelle Java peuvent sûrement résumer ces différences dans une certaine mesure. AFAIK, les modèles de processus et de threads exotiques se trouvent généralement sur des plates-formes anciennes (sur lesquelles C ++ 11 ne sera probablement pas porté) et sur divers systèmes embarqués, qui pourraient avoir une implémentation de bibliothèque de langage spéciale et / ou limitée et également un support de langage limité.Prise en charge des threads
Si les threads ne sont pas pris en charge, il
std::thread::get_id()
doit renvoyer un identifiant non valide (construit par défautstd::thread::id
) car il existe un processus simple, qui n'a pas besoin d'un objet thread pour s'exécuter et le constructeur de astd::thread
doit lancer unstd::system_error
. C'est ainsi que je comprends C ++ 11 en conjonction avec les systèmes d'exploitation actuels. S'il existe un système d'exploitation avec prise en charge des threads, qui ne génère pas de thread principal dans ses processus, faites-le moi savoir.Contrôle des threads
Si l'on a besoin de garder le contrôle sur un thread pour un arrêt correct, on peut le faire en utilisant des primitives de synchronisation et / ou une sorte d'indicateur. Cependant, dans ce cas, définir un indicateur d'arrêt suivi d'une jointure est la façon dont je préfère, car il ne sert à rien d'augmenter la complexité en détachant les threads, car les ressources seraient libérées en même temps de toute façon, là où les quelques octets de l'
std::thread
objet vs une complexité plus élevée et peut-être plus de primitives de synchronisation devraient être acceptables.la source
Considérez le code suivant:
En l'exécutant sur un système Linux, le message de thread_fn n'est jamais imprimé. L'OS nettoie en effet
thread_fn()
dès qu'ilmain()
sort. Le remplacementt1.detach()
part1.join()
imprime toujours le message comme prévu.la source
Le sort du thread après la fermeture du programme est un comportement indéfini. Mais un système d'exploitation moderne nettoiera tous les threads créés par le processus lors de sa fermeture.
Lors du détachement d'un
std::thread
, ces trois conditions continueront de s'appliquer:*this
ne possède plus de threadjoinable()
sera toujours égal àfalse
get_id()
sera égalstd::thread::id()
la source
detach()
un comportement indéfini? Difficile à croire ...Lorsque le thread principal (c'est-à-dire le thread qui exécute la fonction main ()) se termine, le processus se termine et tous les autres threads s'arrêtent.
Référence: https://stackoverflow.com/a/4667273/2194843
la source
Pour permettre à d'autres threads de continuer l'exécution, le thread principal doit se terminer en appelant pthread_exit () plutôt que exit (3). C'est bien d'utiliser pthread_exit dans main. Lorsque pthread_exit est utilisé, le thread principal s'arrêtera de s'exécuter et restera à l'état zombie (défunt) jusqu'à ce que tous les autres threads se terminent. Si vous utilisez pthread_exit dans le thread principal, ne pouvez pas obtenir le statut de retour des autres threads et ne pouvez pas faire de nettoyage pour les autres threads (cela pourrait être fait en utilisant pthread_join (3)). De plus, il est préférable de détacher les threads (pthread_detach (3)) afin que les ressources de thread soient automatiquement libérées à la fin du thread. Les ressources partagées ne seront pas libérées tant que tous les threads ne seront pas fermés.
la source