Le thread de réveil est bloqué lors de l’appel accept ()

Question sur les sockets sur Linux

J’ai un thread de travail qui est bloqué lors d’un appel accept (). Il attend simplement une connexion réseau entrante, la gère, puis revient à l’écoute de la connexion suivante.

Quand il est temps pour le programme de quitter, comment puis-je signaler à ce thread de travail réseau (depuis le thread principal) de revenir de l’appel accept () tout en restant en mesure de sortir de sa boucle et de gérer son code de nettoyage.

Certaines choses que j’ai essayées:

  1. pthread_kill pour envoyer un signal. Cela semble compliqué, mais cela ne permet pas non plus au thread de faire sa logique d’arrêt. Fait également en sorte que le programme se termine. J’aimerais éviter les signaux si possible.

  2. pthread_cancel. Comme ci-dessus. C’est une dure tuerie sur le fil. Cela, et le fil peut faire autre chose.

  3. Fermer le socket d’écoute à partir du thread principal afin de faire accepter accept (). Cela ne fonctionne pas de manière fiable.

Quelques contraintes:

Si la solution consiste à rendre la prise d’écoute non bloquante, c’est très bien. Mais je ne veux pas accepter une solution qui implique que le fil se réveille via un appel sélectif toutes les quelques secondes pour vérifier la condition de sortie.

La condition de thread à quitter peut ne pas être liée à la sortie du processus.

Essentiellement, la logique que je préconise ressemble à ceci.

void* WorkerThread(void* args) { DoSomeImportantInitialization(); // initialize listen socket and some thread specific stuff while (HasExitConditionBeenSet()==false) { listensize = sizeof(listenaddr); int sock = accept(listensocket, &listenaddr, &listensize); // check if exit condition has been set using thread safe semantics if (HasExitConditionBeenSet()) { break; } if (sock < 0) { printf("accept returned %d (errno==%d)\n", sock, errno); } else { HandleNewNetworkCondition(sock, &listenaddr); } } DoSomeImportantCleanup(); // close listen socket, close connections, cleanup etc.. return NULL; } void SignalHandler(int sig) { printf("Caught CTRL-C\n"); } void NotifyWorkerThreadToExit(pthread_t thread_handle) { // signal thread to exit } int main() { void* ptr_ret= NULL; pthread_t workerthread_handle = 0; pthread_create(&workerthread, NULL, WorkerThread, NULL); signal(SIGINT, SignalHandler); sleep((unsigned int)-1); // sleep until the user hits ctrl-c printf("Returned from sleep call...\n"); SetThreadExitCondition(); // sets global variable with barrier that worker thread checks on // this is the function I'm stalled on writing NotifyWorkerThreadToExit(workerthread_handle); // wait for thread to exit cleanly pthread_join(workerthread_handle, &ptr_ret); DoProcessCleanupStuff(); } 

Vous pouvez utiliser un canal pour notifier le thread que vous voulez qu’il quitte. Ensuite, vous pouvez avoir un appel select() qui sélectionne à la fois le tuyau et le socket d’écoute.

Par exemple (comstack mais pas complètement testé):

 // NotifyPipe.h #ifndef NOTIFYPIPE_H_INCLUDED #define NOTIFYPIPE_H_INCLUDED class NotifyPipe { int m_receiveFd; int m_sendFd; public: NotifyPipe(); virtual ~NotifyPipe(); int receiverFd(); void notify(); }; #endif // NOTIFYPIPE_H_INCLUDED // NotifyPipe.cpp #include "NotifyPipe.h" #include  #include  #include  NotifyPipe::NotifyPipe() { int pipefd[2]; int ret = pipe(pipefd); assert(ret == 0); // For real usage put proper check here m_receiveFd = pipefd[0]; m_sendFd = pipefd[1]; fcntl(m_sendFd,F_SETFL,O_NONBLOCK); } NotifyPipe::~NotifyPipe() { close(m_sendFd); close(m_receiveFd); } int NotifyPipe::receiverFd() { return m_receiveFd; } void NotifyPipe::notify() { write(m_sendFd,"1",1); } 

Puis select avec receiverFd() , et notifiez la résiliation à l’aide de notify() .

Fermez le socket en utilisant l’appel shutdown() . Cela réveillera les threads bloqués dessus, tout en maintenant le descripteur de fichier valide.

close() sur un descripteur utilisé par un autre thread que B utilise est insortingnsèquement dangereux: un autre thread C peut ouvrir un nouveau descripteur de fichier que le thread B utilisera à la place du descripteur fermé. dup2() un /dev/null dessus évite ce problème, mais ne réveille pas de manière fiable les threads bloqués.

Notez que shutdown() ne fonctionne que sur les sockets – pour les autres types de descripteurs, vous avez probablement besoin des approches select + pipe-to-self ou annulation.

Fermez le socket en écoute et acceptez retournera une erreur.

Qu’est-ce qui ne fonctionne pas de manière fiable avec cela? Décrivez les problèmes que vous rencontrez.

pthread_cancel pour annuler un thread bloqué dans accept () est risqué si l’implémentation de pthread n’implémente pas correctement l’annulation, c’est-à-dire que si le thread a créé un socket, juste avant de revenir à votre code, un pthread_cancel () est appelé, le thread est annulé, et le socket nouvellement créé est une fuite. Bien que FreeBSD 9.0 et les versions ultérieures ne rencontrent pas un tel problème, vous devriez d’abord vérifier votre système d’exploitation.