Comment rejoindre un thread qui se bloque sur le blocage des entrées-sorties?

J’ai un thread en arrière-plan qui lit les événements d’un périphérique d’entrée de façon bloquante, maintenant, quand je quitte l’application, je veux nettoyer le thread correctement, mais je ne peux pas simplement lancer un pthread_join () ne quitterait jamais en raison de l’IO bloquant.

Comment résoudre correctement cette situation? Dois-je envoyer un pthread_kill (theard, SIGIO) ou un pthread_kill (theard, SIGALRM) pour casser le bloc? Est-ce que l’un de ces signaux est le bon? Ou existe-t-il un autre moyen de résoudre cette situation et de laisser ce thread enfant quitter la lecture bloquante?

Actuellement un peu perplexe car aucun de mes googler n’a trouvé de solution.

Ceci est sous Linux et en utilisant pthreads.

Edit: J’ai joué un peu avec SIGIO et SIGALRM, quand je n’installe pas de gestionnaire de signal, ils cassent les IO bloquantes, mais envoient un message sur la console (“I / O possible”) mais quand j’installe un gestionnaire de signal , pour éviter ce message, ils ne cassent plus l’IO bloquant, de sorte que le thread ne se termine pas. Donc, je suis en quelque sorte de retour à la première étape.

Une vieille question qui pourrait très bien trouver une nouvelle réponse à mesure que les choses ont évolué et qu’une nouvelle technologie est désormais disponible pour mieux gérer les signaux dans les threads.

Depuis le kernel Linux 2.6.22, le système offre une nouvelle fonction appelée signalfd() qui peut être utilisée pour ouvrir un descripteur de fichier pour un ensemble donné de signaux Unix (en dehors de ceux qui tuent un processus).

 // defined a set of signals sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); // ... you can add more than one ... // prevent the default signal behavior (very important) sigprocmask(SIG_BLOCK, &set, nullptr); // open a file descriptor using that set of Unix signal f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC); 

Vous pouvez maintenant utiliser les fonctions poll() ou select() pour écouter le signal avec le descripteur de fichier le plus courant (socket, fichier sur le disque, etc.) que vous écoutiez.

Le NONBLOCK est important si vous voulez une boucle capable de vérifier les signaux et autres descripteurs de fichiers encore et encore (c’est aussi important sur votre autre descripteur de fichier).

J’ai une telle implémentation qui fonctionne avec (1) les temporisateurs, (2) les sockets, (3) les pipes, (4) les signaux Unix, (5) les fichiers réguliers. En fait, vraiment tout descripteur de fichier plus les timers.

https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.cpp
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.h

Vous pouvez également être intéressé par des bibliothèques telles que libevent

Je recommande également d’utiliser un moyen de sélection ou d’autres moyens non basés sur le signal pour terminer votre thread. L’une des raisons pour lesquelles nous avons des discussions est d’essayer de sortir de la folie des signaux. Cela dit…

Généralement, on utilise pthread_kill () avec SIGUSR1 ou SIGUSR2 pour envoyer un signal au thread. Les autres signaux suggérés – SIGTERM, SIGINT, SIGKILL – ont une sémantique à l’échelle du processus qui ne vous intéresse peut-être pas.

En ce qui concerne le comportement lorsque vous avez envoyé le signal, je suppose que cela a à voir avec la façon dont vous avez traité le signal. Si vous n’avez pas de gestionnaire installé, l’action par défaut de ce signal est appliquée, mais dans le contexte du thread qui a reçu le signal. Ainsi, SIGALRM, par exemple, serait “géré” par votre thread, mais le traitement consisterait à terminer le processus – probablement pas le comportement souhaité.

La réception d’un signal par le thread le décompose généralement d’une lecture avec EINTR, sauf s’il est vraiment dans cet état ininterrompu, comme mentionné dans une réponse précédente. Mais je pense que ce n’est pas le cas, ou vos expériences avec SIGALRM et SIGIO n’auraient pas mis fin au processus.

Votre lecture est peut-être dans une sorte de boucle? Si la lecture se termine avec -1 retour, alors sortez de cette boucle et quittez le thread.

Vous pouvez jouer avec ce code très flou que j’ai mis en place pour tester mes suppositions – je suis en ce moment à deux fuseaux horaires de mes livres POSIX …

 #include  #include  #include  #include  int global_gotsig = 0; void *gotsig(int sig, siginfo_t *info, void *ucontext) { global_gotsig++; return NULL; } void *reader(void *arg) { char buf[32]; int i; int hdlsig = (int)arg; struct sigaction sa; sa.sa_handler = NULL; sa.sa_sigaction = gotsig; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); if (sigaction(hdlsig, &sa, NULL) < 0) { perror("sigaction"); return (void *)-1; } i = read(fileno(stdin), buf, 32); if (i < 0) { perror("read"); } else { printf("Read %d bytes\n", i); } return (void *)i; } main(int argc, char **argv) { pthread_t tid1; void *ret; int i; int sig = SIGUSR1; if (argc == 2) sig = atoi(argv[1]); printf("Using sig %d\n", sig); if (pthread_create(&tid1, NULL, reader, (void *)sig)) { perror("pthread_create"); exit(1); } sleep(5); printf("killing thread\n"); pthread_kill(tid1, sig); i = pthread_join(tid1, &ret); if (i < 0) perror("pthread_join"); else printf("thread returned %ld\n", (long)ret); printf("Got sig? %d\n", global_gotsig); } 

La manière canonique de le faire est d’utiliser pthread_cancel , où le thread a effectué pthread_cleanup_push / pop pour nettoyer toutes les ressources qu’il utilise.

Malheureusement, cela ne peut PAS être utilisé dans le code C ++, jamais. Tout code de librairie std en C ++, ou ANY try {} catch() sur la stack appelante au moment de pthread_cancel risque de tuer tout votre processus.

La seule solution consiste à gérer SIGUSR1 , en définissant un indicateur d’arrêt, pthread_kill(SIGUSR1) , puis partout où le thread est bloqué sur les E / S, si vous obtenez EINTR vérifiez l’indicateur d’arrêt avant de réessayer. En pratique, cela ne réussit pas toujours sous Linux, je ne sais pas pourquoi.

Mais dans tous les cas, il est inutile de parler si vous devez appeler une librairie tierce, car ils auront probablement une boucle serrée qui redémarre simplement les E / S sur EINTR . Inverser l’ingénierie de leur descripteur de fichier pour le fermer ne le réduira pas non plus – ils pourraient attendre un sémaphore ou une autre ressource. Dans ce cas, il est tout simplement impossible d’écrire du code de travail, point. Oui, c’est complètement endommagé par le cerveau. Parlez aux gars qui ont conçu les exceptions C ++ et pthread_cancel . Soi-disant, cela pourrait être corrigé dans une future version de C ++. Bonne chance avec ça.

Votre select() peut avoir un délai d’attente, même si c’est rare, afin de quitter le thread avec élégance dans certaines conditions. Je sais, le sondage craint …

Une autre solution consiste à avoir un canal pour chaque enfant et à l’append à la liste des descripteurs de fichiers surveillés par le thread. Envoyez un octet au canal depuis le parent lorsque vous souhaitez que cet enfant quitte. Pas d’interrogation au prix d’un tuyau par fil.

Dépend de la façon dont il attend pour IO.

Si le thread est dans l’état “IO sans interruption” (indiqué par “D” en haut), alors vous ne pouvez vraiment rien y faire. Les threads n’entrent normalement que brièvement dans cet état, en faisant quelque chose comme attendre qu’une page soit échangée (ou chargée à la demande, par exemple depuis un fichier mmap’d ou une bibliothèque partagée, etc.), mais un échec (en particulier d’un serveur NFS) peut provoquer pour restr dans cet état plus longtemps.

Il n’y a aucun moyen de s’échapper de cet état “D”. Le thread ne répondra pas aux signaux (vous pouvez les envoyer, mais ils seront mis en queue).

S’il s’agit d’une fonction IO normale telle que read (), write () ou une fonction en attente telle que select () ou poll (), les signaux seront livrés normalement.

Une des solutions qui m’a frappé la dernière fois que j’avais un problème comme celui-ci était de créer un fichier (par exemple un tube) qui n’existait que dans le but de réveiller des threads bloquants.

L’idée serait de créer un fichier à partir de la boucle principale (ou 1 par thread, comme le suggère le timeout – cela vous donnerait un contrôle plus précis sur les threads qui sont réveillés). Tous les threads qui bloquent les E / S de fichiers feraient un select (), en utilisant le ou les fichiers sur lesquels ils essaient de fonctionner, ainsi que le fichier créé par la boucle principale (en tant que membre de la lecture). ensemble de descripteurs de fichiers). Cela devrait faire revenir tous les appels select ().

Code pour gérer cet “événement” de la boucle principale devrait être ajouté à chacun des threads.

Si la boucle principale devait réveiller tous les threads, elle pourrait soit écrire dans le fichier, soit la fermer.


Je ne peux pas dire avec certitude si cela fonctionne, car une restructuration signifiait que la nécessité de l’essayer disparaissait.

Je pense, comme vous l’avez dit, que le seul moyen serait d’envoyer un signal, puis de le prendre et de le traiter de manière appropriée. Les alternatives peuvent être SIGTERM, SIGUSR1, SIGQUIT, SIGHUP, SIGINT, etc.

Vous pouvez également utiliser select () sur votre descripteur d’entrée afin de ne lire que lorsqu’il est prêt. Vous pouvez utiliser select () avec un timeout de, disons, une seconde, puis vérifier si ce thread doit finir.

J’ajoute toujours une fonction ” kill ” liée à la fonction de thread que je lance avant la jointure, ce qui garantit que le thread pourra être joint dans un délai raisonnable. Lorsqu’un thread utilise le blocage des entrées-sorties, j’essaie d’utiliser le système pour briser le verrou. Par exemple, lorsque vous utilisez un socket, j’appelle kill call shutdown (2) ou close (2) sur celui-ci, ce qui entraînerait la fin de la stack réseau.

L’implémentation de socket Linux est thread-safe.

Je suis surpris que personne n’ait suggéré pthread_cancel. J’ai récemment écrit un programme d’E / S multithread et l’appel à cancel () et la jointure () ont tout simplement fonctionné.

A l’origine, j’avais essayé le pthread_kill () mais j’ai fini par terminer tout le programme avec les signaux testés.

Si vous bloquez dans une bibliothèque tierce qui boucle sur EINTR, vous pouvez envisager une combinaison de l’utilisation de pthread_kill avec un signal (USR1 etc) appelant une fonction vide (pas SIG_IGN) avec la fermeture / remplacement du descripteur de fichier dans question. En utilisant dup2 pour remplacer le fichier fd par / dev / null ou similaire, la bibliothèque tierce obtiendra un résultat de fin de fichier lorsqu’elle réessayera la lecture.

Notez que dup () en utilisant d’abord le socket d’origine, vous pouvez éviter d’avoir à fermer le socket.

Les signaux et les threads constituent un problème subtil sous Linux en fonction des différentes pages de manuel. Utilisez-vous LinuxThreads ou NPTL (si vous êtes sous Linux)?

Je ne suis pas sûr de cela, mais je pense que le gestionnaire de signaux affecte l’ensemble du processus, donc soit vous terminez tout votre processus, soit tout continue.

Vous devez utiliser select ou poll time et définir un indicateur global pour terminer votre thread.

Je pense que l’approche la plus propre aurait le thread utilisant des variables conditionnelles dans une boucle pour continuer.

Lorsqu’un événement d’E / S est déclenché, le conditionnel doit être signalé.

Le thread principal pourrait simplement signaler la condition en modifiant le prédicat de la boucle sur false.

quelque chose comme:

 while (!_finished) { pthread_cond_wait(&cond); handleio(); } cleanup(); 

Rappelez-vous avec des variables conditionnelles pour gérer correctement les signaux. Ils peuvent avoir des choses telles que des «réveils parasites». Donc, je voudrais emballer votre propre fonction autour de la fonction cond_wait.

 struct pollfd pfd; pfd.fd = socket; pfd.events = POLLIN | POLLHUP | POLLERR; pthread_lock(&lock); while(thread_alive) { int ret = poll(&pfd, 1, 100); if(ret == 1) { //handle IO } else { pthread_cond_timedwait(&lock, &cond, 100); } } pthread_unlock(&lock); 

thread_alive est une variable spécifique au thread qui peut être utilisée en combinaison avec le signal pour tuer le thread.

en ce qui concerne la section du handle IO, vous devez vous assurer que vous avez ouvert avec l’option O_NOBLOCK ou, si c’est un socket, il existe un indicateur similaire que vous pouvez définir MSG_NOWAIT ??. pour d’autres fds im pas sûr