web-dev-qa-db-fra.com

Qu'advient-il d'un thread détaché lorsque main () se termine?

Supposons que je commence un std::thread, Puis detach(), le thread continue donc à s'exécuter même si le std::thread Qui le représentait une fois sort de son cadre.

Supposons en outre que le programme ne dispose pas d'un protocole fiable pour rejoindre le thread détaché1, de sorte que le thread détaché continue à s'exécuter lorsque main() se ferme.

Je ne trouve rien dans la norme (plus précisément dans le brouillon N3797 C++ 14) décrivant ce qui devrait arriver, ni 1.10 ni 30.3 ne contiennent une formulation pertinente.

1 Une autre question, probablement équivalente, est la suivante: "un thread détaché peut-il être rejoint à nouveau", car quel que soit le protocole que vous souhaitez inventer, la partie signalisation doit être effectuée pendant que le thread est toujours en cours d'exécution, et le planificateur de système d'exploitation peut décider mettre le thread en veille pendant une heure juste après la signalisation, sans que le destinataire puisse détecter de manière fiable que le thread s'est réellement terminé.

Si le fait de manquer de main() avec des threads détachés en cours d'exécution est un comportement indéfini, alors toute utilisation de std::thread::detach() est indéfinie comportement à moins que le fil principal ne quitte jamais2.

Ainsi, manquer de main() avec des threads détachés en cours d'exécution doit avoir des effets définis . La question est: (dans le standard C++ , pas POSIX, OS Docs, ...) sont ces effets définis.

2 Un thread 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 de std::packaged_task, Ou par un sémaphore de comptage ou un indicateur et une condition variable), mais cela ne garantit pas que le thread a fini d'exécuter . En effet, à moins de placer la partie signalisation dans le destructeur du premier objet automatique du fil, il y aura , en général, un code (destructeurs) qui exécutez après le code de signalisation. Si le système d'exploitation planifie le thread principal pour consommer le résultat et quitter avant que le thread détaché ne finisse d'exécuter lesdits destructeurs, qu'est-ce que ^ Wis définira comme se produisant?

137

La réponse à la question initiale "que se passe-t-il pour un thread détaché lorsque main() exits" est:

Il continue à s'exécuter (car le standard ne dit pas qu'il est arrêté), et c'est bien défini, tant qu'il ne touche ni aux variables (automatiques | thread_local) des autres threads ni aux objets statiques.

Cela semble être autorisé à autoriser les gestionnaires de threads en tant qu'objets statiques (remarque dans [basic.start.term]/4 le dit, merci à @dyp pour le pointeur).

Des problèmes surviennent lorsque la destruction d'objets statiques est terminée, car l'exécution entre alors dans un régime où seul le code autorisé dans les gestionnaires de signaux peut être exécuté ([basic.start.term]/1, 1ère phrase). Dans la bibliothèque standard C++, il ne s’agit que de la bibliothèque <atomic> ([support.runtime]/9, 2ème phrase). En particulier, cela - en général --exclutcondition_variable (Sa définition est définie par l'implémentation si elle est sauvegardée pour être utilisée dans un gestionnaire de signaux, car elle ne fait pas partie de <atomic>) .

À moins que vous n'ayez 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 jamais être rejoints" est la suivante:

Oui, avec la famille de fonctions *_at_thread_exit (notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(), ...).

Comme indiqué dans la note de bas de page [2] de la question, signaler une variable de condition, un sémaphore ou un compteur atomique ne suffit pas pour joindre un thread détaché (dans le sens où il est garanti que la fin de son exécution s'est passé - before la réception de cette 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 threads.

Exécuter la signalisation comme la dernière chose que fait le thread (after destructeurs d'objets automatiques et thread-locaux has-happen) est ce que la famille de fonctions _at_thread_exit a été conçu pour.

Ainsi, afin d'éviter un comportement indéfini en l'absence de toute garantie d'implémentation supérieure à celle requise par la norme, vous devez rejoindre (manuellement) un thread séparé avec une fonction _at_thread_exit Faisant la signalisation o = faire exécuter le thread détaché niquement code qui serait également sûr pour un gestionnaire de signal.

43
Marc Mutz - mmutz

Détacher les fils

Selon std::thread::detach :

Sépare le thread d'exécution de l'objet thread, permettant ainsi à l'exécution de continuer de manière indépendante. Toutes les ressources allouées seront libérées une fois le thread terminé.

De pthread_detach :

La fonction pthread_detach () indiquera à l'implémentation que le stockage du thread peut être récupéré lorsque ce thread se termine. Si le thread n'est pas terminé, pthread_detach () ne le fera pas se terminer. L'effet de plusieurs appels pthread_detach () sur le même thread cible n'est pas spécifié.

Le détachement de threads sert principalement à économiser les ressources, au cas où l’application n’aurait pas besoin d’attendre la fin d’un thread (par exemple, les démons, qui doivent être exécutés jusqu’à la fin du processus):

  1. Pour libérer le descripteur côté application: On peut laisser un objet std::thread sortir de son champ d'application sans se joindre, ce qui conduit normalement à un appel à std::terminate() lors de la destruction.
  2. Pour permettre au système d'exploitation de nettoyer les ressources spécifiques au thread ( TCB ) automatiquement dès que le thread se ferme, car nous l'avons explicitement spécifié, ce qui ne nous intéresse pas rejoindre le fil plus tard, on ne peut donc pas rejoindre un fil déjà détaché.

Tuer des fils

Le comportement à la fin du processus est identique à celui du thread principal, ce qui pourrait au moins intercepter certains signaux. Que d'autres threads puissent ou non gérer les signaux n'est pas très important, car on pourrait joindre 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éconnecté 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 ne cherche pas à définir le comportement exact du système d'exploitation sous-jacent, alors que les développeurs d'un Java VM peuvent sûrement faire abstraction Les modèles AFAIK, de processus exotiques et de threading se trouvent généralement sur des plates-formes anciennes (sur lesquelles C++ 11 ne sera probablement pas porté) et divers systèmes embarqués, qui pourraient avoir une implémentation particulière et/ou limitée de la bibliothèque de langues et également une prise en charge linguistique limitée.

Support de filetage

Si les threads ne sont pas supportés std::thread::get_id() devrait renvoyer un identifiant invalide (construit par défaut std::thread::id) Car il existe un processus simple, ne nécessitant pas d'objet thread pour être exécuté et le constructeur d'un std::thread devrait lancer un std::system_error . C'est ainsi que je comprends C++ 11 en liaison avec les systèmes d'exploitation actuels. Si un système d'exploitation prend en charge les threads, mais qu'il ne génère pas de thread principal dans ses processus, faites-le moi savoir.

Contrôler les fils

Si vous devez garder le contrôle d'un thread pour un arrêt correct, vous pouvez 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 ma préférence, car il est inutile d'accroître la complexité en détachant des threads, car les ressources seraient libérées de toute façon, où les quelques octets de - std::thread objet par rapport à une complexité plus élevée et éventuellement plus de primitives de synchronisation devraient être acceptables.

40
Sam

Le destin du thread après la sortie 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.

Lorsque vous détachez un std::thread, ces trois conditions resteront valables

  1. *this ne possède plus de fil
  2. joinable () sera toujours égal à false
  3. get_id () sera égal à std :: thread :: id ()
17
Caesar

Considérons le code suivant:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>

void thread_fn() {
  std::this_thread::sleep_for (std::chrono::seconds(1)); 
  std::cout << "Inside thread function\n";   
}

int main()
{
    std::thread t1(thread_fn);
    t1.detach();

    return 0; 
}

En l'exécutant sur un système Linux, le message de thread_fn n'est jamais imprimé. Le système d'exploitation nettoie en effet thread_fn() dès que main() se ferme. Remplacer t1.detach() par t1.join() imprime toujours le message comme prévu.

15
kgvinod

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/219484

5
funk

Pour permettre à d'autres threads de continuer à s'exécuter, 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 cessera de s'exécuter et restera à l'état zombie (défunt) jusqu'à ce que tous les autres threads soient fermés. Si vous utilisez pthread_exit dans le thread principal, vous ne pouvez pas obtenir le statut de retour des autres threads ni le nettoyage d'autres threads (vous pouvez utiliser pthread_join (3)). En outre, il est préférable de détacher les threads (pthread_detach (3)) afin que les ressources de threads soient automatiquement libérées à la fin du thread. Les ressources partagées ne seront pas libérées tant que tous les threads n'auront pas quitté.

0
yshi