web-dev-qa-db-fra.com

Comment quitter proprement un programme threadé C ++?

Je crée plusieurs threads dans mon programme. En appuyant sur Ctrl-C, un gestionnaire de signal est appelé. Dans un gestionnaire de signal, j'ai enfin mis exit(0). Le problème est que parfois le programme se termine en toute sécurité, mais les autres fois, je reçois une erreur d'exécution indiquant

abort() has been called

Alors, quelle serait la solution possible pour éviter l'erreur?

31
Rishav Jain

La manière habituelle est de définir un drapeau atomique (comme std::atomic<bool>) qui est vérifié par tous les threads (y compris le thread principal). Si cette option est définie, les sous-threads se terminent et le thread principal commence à join les sous-threads. Alors vous pouvez sortir proprement.

Si tu utilises std::thread pour les discussions, c'est une des raisons possibles des plantages que vous avez. Vous devez join le fil avant le std::thread l'objet est détruit.

30

D'autres ont mentionné que le gestionnaire de signal avait défini un std::atomic<bool> Et que tous les autres threads vérifiaient périodiquement cette valeur pour savoir quand quitter.

Cette approche fonctionne bien tant que tous vos autres fils se réveillent périodiquement de toute façon, à une fréquence raisonnable.

Ce n'est pas entièrement satisfaisant si un ou plusieurs de vos threads sont purement événementiels. Cependant, dans un programme événementiel, les threads ne sont censés se réveiller que lorsqu'ils ont du travail à faire, ce qui signifie qu'ils risquent fort être endormi pendant des jours ou des semaines à la fois. S'ils sont forcés de se réveiller toutes les millisecondes, il suffit simplement d'interroger un indicateur atomic-boolean-flag, ce qui rend un programme par ailleurs extrêmement efficace en ressources processeur beaucoup moins, puisque chaque thread se réveille maintenant à de brefs intervalles réguliers, 24/7/365. Cela peut être particulièrement problématique si vous essayez de préserver la durée de vie de la batterie, car cela peut empêcher le processeur de passer en mode d'économie d'énergie.

Une approche alternative qui évite les sondages serait celle-ci:

  1. Au démarrage, demandez à votre thread principal de créer un fd-pipe ou une paire de socket (en appelant pipe () ou socketpair () )
  2. Demandez à votre thread principal (ou éventuellement à un autre thread responsable) d'inclure le socket de réception dans son select()fd_set prêt à la lecture (ou effectuez une action similaire pour poll() ou quelque chose d'attendre Fonction IO que le thread bloque dedans)
  3. Lorsque le gestionnaire de signal est exécuté, faites-lui écrire un octet (n'importe quel octet, peu importe) dans le socket d'envoi.
  4. Cela fera revenir immédiatement l'appel select() du thread principal, avec FD_ISSET(receivingSocket) indiquant la valeur true à cause de l'octet reçu.
  5. À ce stade, votre thread principal sait qu'il est temps de quitter le processus. Il peut donc commencer à arrêter tous ses threads enfants pour qu'il s'arrête (via le mécanisme approprié: booléens atomiques ou pipes ou autre chose).
  6. Après avoir dit à tous les threads enfants de commencer à s’arrêter, le thread principal doit ensuite appeler join() sur chaque thread enfant, afin de pouvoir garantir que tous les threads ont réellement disparu avant main () retourne. (Cela est nécessaire car sinon, il y a un risque de situation de concurrence critique - par exemple, le code de nettoyage post-main () peut parfois libérer une ressource alors qu'un thread enfant toujours en cours d'utilisation l'utilisait toujours, ce qui conduisait à un crash).
14
Jeremy Friesner

La première chose que vous devez accepter est que le filetage est difficile.

Un "programme utilisant des threads" est à peu près aussi générique qu'un "programme utilisant de la mémoire", et votre question est similaire à "comment ne pas corrompre la mémoire d'un programme utilisant de la mémoire?"

La façon dont vous gérez le problème des threads consiste à restreindre votre utilisation des threads et leur comportement.

Si votre système de filetage est constitué d'un ensemble de petites opérations composées dans un réseau de flux de données, avec la garantie implicite que si une opération est trop grande, elle est scindée en opérations plus petites et/ou effectue des points de contrôle avec le système, puis son arrêt est très différent. que si vous avez un thread qui charge un fichier externe DLL) qui l’exécute ensuite pendant une durée allant de 1 seconde à 10 heures à une longueur infinie.

Comme la plupart des choses en C++, la résolution de votre problème va concerner la propriété, le contrôle et (en dernier ressort) des piratages.

Comme les données en C++, chaque thread doit appartenir. Le propriétaire d'un thread doit avoir un contrôle important sur ce thread et pouvoir lui dire que l'application est en cours de fermeture. Le mécanisme d'arrêt doit être robuste et testé, et idéalement connecté à d'autres mécanismes (comme un abandon précoce de tâches spéculatives).

Le fait que vous appeliez exit (0) est un mauvais signe. Cela implique que votre thread d'exécution principal n'a pas de chemin d'arrêt propre. Commencez par là le gestionnaire d'interruption doit signaler au thread principal que le processus d'arrêt doit commencer, puis que le thread principal doit s'arrêter normalement. Tous les cadres de pile doivent se dérouler, les données doivent être nettoyées, etc.

Ensuite, le même type de logique permettant un arrêt net et rapide devrait également être appliqué à votre code threadé.

Quiconque vous le dit est aussi simple qu’une variable de condition/booléen atomique et polling vous vend un bon de commande. Cela ne fonctionnera que dans des cas simples si vous êtes chanceux et il sera très difficile de déterminer s'il fonctionne de manière fiable.

10

En plus de Certains types de programmeur et liés à la discussion dans la section commentaire, vous devez créer le drapeau qui contrôle l’arrêt de vos discussions comme atomic type.

Considérons le cas suivant:

bool done = false;
void pending_thread()
{
    while(!done)
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

Ici, le thread de travail peut également être votre main et done est un drapeau de fin de votre thread, mais le thread en attente doit faire quelque chose avec les données données par le thread de travail avant de quitter.

cet exemple est associé à une situation de concurrence critique et à un comportement indéfini, et il est vraiment difficile de trouver le problème réel dans le monde réel.

Maintenant la version corrigée avec std::automic:

std::atomic<bool> done(false);
void pending_thread()
{
    while(!done.load())
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

Vous pouvez quitter le thread sans vous préoccuper de la condition de concurrence ou de UB.

3
HMD