3
votes

C ++ Tuer un std :: thread

J'ai un programme qui crée un fil qui écoute un événement. Il existe une situation dans laquelle ce fil ne recevra jamais cet événement et je dois y mettre fin. Je sais comment attraper le cas où il ne recevra pas cet événement.

J'ai créé un std :: thread , mais je n'ai rien trouvé sur la façon de terminer le thread. J'ai essayé d'appeler

t.detach()

Et puis de laisser le destructeur faire le travail, mais je ne peux pas nettoyer les ressources allouées par le fil de cette façon.

Mon les questions sont: 1. Existe-t-il un moyen d'envoyer un SIGKILL ou équivalent à un std :: thread pour le tuer? Et 2. Existe-t-il un moyen d’attraper ce signal depuis le thread pour nettoyer les ressources?

La question posée (et la réponse) Comment mettre fin à un thread en C ++ 11? ne répond pas à ma question car je souhaite terminer le thread depuis son parent, pas de l'intérieur du thread.

De plus, mon problème est que mon thread de travail peut soit attendre un appel système bloquant (c'est-à-dire un paquet entrant) ou une variable conditionnelle, c'est pourquoi c'est délicat.


4 Réponses :


2
votes

En général, tuer un thread est un comportement indésirable, vous voulez lancer le thread, faire l'implémentation nécessaire en fonction de la condition, et si la condition ne satisfait pas, vous pouvez le quitter proprement. Utilisez probablement std :: condition_variable ... et définissez la condition à partir du thread principal afin que l'enfant puisse quitter en fonction de certaines conditions.


1 commentaires

Comme j'ai mon thread en attente d'un événement, je ne peux pas le faire dormir sur une variable conditionnelle.



1
votes

Il n'y a pas de moyen standard de tuer un thread, mais cela dépend de la plate-forme.

Sous Windows, vous pouvez utiliser TerminateThread par exemple.

Cependant, soyez avisé, à moins que vous ne connaissiez les effets secondaires, etc., c'est souvent un anti-pattern pour le faire.


1 commentaires

Merci, je vais essayer de contourner ce problème sans utiliser de motifs anti.



2
votes

Vous ne pouvez pas tuer un thread par les moyens standards du C ++ et, en général, ce n'est pas un comportement souhaitable. Vous pouvez utiliser des variables conditionnelles pour obtenir un comportement similaire avec un "système de signalisation" :

#include <mutex>
#include <thread>
#include <atomic>
#include <iostream>

std::mutex m;
std::condition_variable cv;
std::atomic_bool state;

void worker_thread() {
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, [] { //waits for the notify_one "signal"
        return state.load(); //returns false if the waiting should be continued. 
    });
    std::cout << "Finished waiting..." << std::endl;
}

int main() {
    state.store(false); //set initial state
    std::thread worker(worker_thread);

    std::cout << "Start waiting..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2)); //do some work here
    {
        std::lock_guard<std::mutex> lk(m); //avoids cv to get notified before been in wait state
        state.store(true);
        cv.notify_one(); //emit the signal once "state" var is true
    }

    worker.join();
    return 0;
}


6 commentaires

OP a dit que le thread est mis en file d'attente sur un événement - cette réponse suppose qu'il n'attend pas indéterminé.


Il a dit "Je sais comment attraper l'affaire", puis il peut le notifier.


Mais alors il pourrait simplement le laisser aller jusqu'à la fin - cela semble, au mieux, peu clair.


Ok, d'accord qu'il pourrait faire ça. Mais peut-être qu'il ne peut pas atteindre la fin du fil. S'il crée un «système de signaux», il peut obtenir un comportement similaire. Une fois qu'il n'a pas publié la situation spécifique, où il pourrait peut-être résoudre cela avec un if , j'ai publié une solution générale.


Le thread attend un événement, donc l'événement le réveillera. Je ne peux pas faire dormir le fil à condition.


Cette condition pourrait être votre événement. L'effet serait le même. Vous n'avez vraiment pas besoin de tuer votre thread si vous pouvez contrôler ce qu'il fait. Les variables de condition sont suffisantes.



2
votes
  1. Existe-t-il un moyen d'envoyer un SIGKILL ou l'équivalent d'un std :: thread pour le tuer?

Un tel équivalent n'existe pas; au moins un standard.

  1. Existe-t-il un moyen de capturer ce signal depuis le thread pour nettoyer les ressources?

Il n'y a pas de méthode standard en C ++ pour signaler un thread.

POSIX a pthread_kill qui peut être utilisé pour signaler un thread. Notez que les signaux "arrêter", "continuer" ou "terminer" affectent l'ensemble du processus, donc ce n'est pas exactement ce que vous demandez.

Cependant, les gestionnaires de signaux asynchrones sont limités sur ce qu'ils peuvent faire et le nettoyage des ressources ne sera pas possible. Ce que vous devez faire est de faire savoir au thread qu'il doit se terminer, et laissez-le s'arrêter et nettoyer volontairement.

fil qui écoute un événement.

La solution dépend du type d'écoute que nous envisageons. S'il s'agit d'une variable de condition, vous pouvez définir un booléen atomique pour demander l'arrêt et réveiller le thread en le notifiant.

Si l'écoute est un appel système bloquant, la solution est un peu plus délicate. Le grand avantage de lever un signal est qu'il interrompt le blocage des appels système. Vous pouvez utiliser le gestionnaire pour définir une variable volatile sig_atomic_t que le thread peut lire et renvoyer volontairement. L'interruption peut donner à votre thread une chance de vérifier la variable avant de reprendre l'attente. La mise en garde est que nous devons transformer ce peut en garantie:

Vous devez enregistrer le gestionnaire de signaux en utilisant sigaction (fonction POSIX standard; pas C ++ standard) avec SA_RESTART désactivé. Sinon, selon les valeurs par défaut du système, l'appel système pourrait reprendre au lieu de revenir.


Une autre approche consiste à envoyer l'événement que l'autre thread attend. Cela peut être plus simple ou plus délicat que ci-dessus selon le type d'événement que nous envisageons. Par exemple, lors de l'écoute sur une prise, il est généralement possible de se connecter à cette prise.


Il existe une proposition pour introduire le wrapper de thread std :: jthread dans un futur standard C ++, qui offre un moyen portable de demander à un thread de s'arrêter. Malheureusement, je ne sais pas quelles garanties il y aurait concernant le blocage des appels système.


14 commentaires

J'adore la façon dont les groupes de normes prenant des propositions de nouvelles idées de mise en œuvre pour bander les échecs des propositions précédentes adoptées par les normes précédentes sont discutés sans même un soupçon d'ironie. Pas de progrès, mais des emplois pour la vie.


Merci pour la réponse détaillée. L'événement est parfois un appel système et à d'autres moments une variable de condition. Je pense que vous m'avez donné assez pour travailler, merci!


@ArikRinberg L'attente sur une variable de condition est probablement un appel système en interne, donc l'approche de signalisation devrait également fonctionner pour ce cas.


C'est vrai, mais ils sont traités différemment. Je peux facilement signaler une variable conditionnelle. Je dois faire beaucoup plus de travail pour envoyer un paquet "kill" au processus.


@ArikRinberg Mais l'alternative d'envoyer un signal au thread et de définir une variable fonctionne dans les deux cas (en supposant que l'attente conditionnelle est interrompue comme je le soupçonne).


Je ne suis pas votre logique. Comment la signalisation d'une variable conditionnelle d'espace utilisateur pouvait-elle interrompre une condition d'espace noyau?


@ArikRinberg Qu'entendez-vous par condition d'espace noyau et variable conditionnelle d'espace utilisateur ?


@eerorika Pour reprendre ce que vous avez dit, cette variable de condition est probablement un appel système en interne. Il y a une grande différence entre s'accrocher à une variable de condition à laquelle j'ai accès et s'accrocher à un paquet sur lequel je n'ai pas de contrôle.


@ArikRinberg Quelle est la différence? Si vous attendez le paquet avec listen , l'appel sera interrompu par le gestionnaire de signal. Si vous attendez une variable conditiona avec std :: condition_variable :: wait , cela devrait également être interrompu. Ce que vous faites n'a pas d'importance tant que l'appel de blocage est interrompu et non repris.


Je pense que la méthode que vous avez décrite qui utilise EINTR est soumise à une condition de concurrence: si le signal est reçu avant d'entrer dans l'appel système de blocage mais après avoir vérifié le drapeau, le thread ne se terminera pas immédiatement comme souhaité et attendra à la place un autre signal ou plus d'entrée. Si vous effectuez la vérification de l'indicateur juste avant l'appel de blocage, cela peut être une petite fenêtre de temps, mais elle est toujours non nulle.


@hddh bon point. Je suppose que l'approche d'envoi de l'événement attendu est supérieure à cet égard lorsque l'événement est stocké dans une file d'attente.


Je pense avoir trouvé une solution dans le cas où le thread se bloque lors d'un appel read (): avant de signaler le thread, utilisez fcntl pour rendre le descripteur de fichier non bloquant. Vous pouvez alors également vérifier le drapeau après l'appel de lecture. Dans ce cas, si le signal arrivait dans cette fenêtre de temps vraiment gênante, ce ne serait pas un problème puisque l'appel de lecture reviendrait immédiatement et vous permettrait de vérifier le drapeau. Selon le scénario, cela peut être plus facile que l'astuce d'auto-pipe.


@hddh Bonne idée! Mais est-ce que fcntl lui-même est garanti d'être visible pour l'autre thread? Je ne suis pas assez à l'aise avec le modèle de mémoire C ++ pour supposer cela. Peut-être avec un std :: atomic_thread_fence entre le réglage non bloquant et la signalisation juste pour être sûr.


Étant donné que fcntl est un appel système qui modifie les indicateurs à l'intérieur du noyau lui-même, je suppose qu'il n'y a pas de problèmes de barrière de mémoire. Une fois l'appel système revenu, les modifications doivent être appliquées.