Reaktivierungs-Thread blockiert beim Aufruf von accept()

Lesezeit: 5 Minuten

Benutzer-Avatar
Selbie

Sockets auf Linux-Frage

Ich habe einen Worker-Thread, der bei einem Accept()-Aufruf blockiert wird. Es wartet einfach auf eine eingehende Netzwerkverbindung, verarbeitet sie und kehrt dann zum Lauschen auf die nächste Verbindung zurück.

Wenn das Programm beendet werden muss, wie signalisiere ich diesem Netzwerkarbeiter-Thread (vom Haupt-Thread), dass er vom Aufruf von accept() zurückkehrt, während ich immer noch in der Lage bin, seine Schleife ordnungsgemäß zu beenden und seinen Bereinigungscode zu verarbeiten.

Einige Dinge, die ich versucht habe:

  1. pthread_kill, um ein Signal zu senden. Fühlt sich klumpig an, dies zu tun, und es erlaubt dem Thread nicht zuverlässig, seine Abschaltlogik auszuführen. Bewirkt auch, dass das Programm beendet wird. Signale möchte ich nach Möglichkeit vermeiden.

  2. pthread_cancel. Das gleiche wie oben. Es ist ein harter Kill für den Thread. Das, und der Thread tut möglicherweise etwas anderes.

  3. Schließen des Listen-Sockets aus dem Haupt-Thread, um accept() abzubrechen. Das funktioniert nicht zuverlässig.

Einige Einschränkungen:

Wenn die Lösung darin besteht, den Listen-Socket nicht blockierend zu machen, ist das in Ordnung. Aber ich möchte keine Lösung akzeptieren, bei der der Thread alle paar Sekunden über einen Auswahlaufruf aufwacht, um die Beendigungsbedingung zu überprüfen.

Die Thread-Bedingung zum Beenden darf nicht an das Beenden des Prozesses gebunden sein.

Im Wesentlichen sieht die Logik, die ich anstrebe, so aus.

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();

}

  • pthread_kill() sendet ein Signal an einen Thread, nicht mehr und nicht weniger. Und es muss nicht unbedingt “lässt das Programm enden“.

    – alk

    4. Juli 2016 um 15:33 Uhr

  • pthread_cancel. Das gleiche wie oben.„Äh, was? Nein, pthread_cancel() macht dann was ganz anderes pthread_kill()! Bitte RTFM.

    – alk

    4. Juli 2016 um 15:34 Uhr


  • Ein einfacher Weg, um accept() zurückzugeben, wäre, eine TCP-Verbindung zu dem Port zu öffnen, auf dem der accept()-Aufruf lauscht.

    – Jeremy Friesner

    6. Dezember 2016 um 16:22 Uhr

Schließen Sie den Socket mit dem shutdown() Anruf. Dadurch werden alle darauf blockierten Threads aktiviert, während der Dateideskriptor gültig bleibt.

close() auf einem Deskriptor, den ein anderer Thread B verwendet, ist von Natur aus gefährlich: Ein anderer Thread C kann einen neuen Dateideskriptor öffnen, den Thread B dann anstelle des geschlossenen verwendet. dup2() a /dev/null on vermeidet dieses Problem, weckt aber blockierte Threads nicht zuverlässig auf.

Beachten Sie, dass shutdown() funktioniert nur auf Sockets – für andere Arten von Deskriptoren benötigen Sie wahrscheinlich die Ansätze select+pipe-to-self oder Cancellation.

Benutzer-Avatar
Douglas Leder

Sie können eine Pipe verwenden, um den Thread darüber zu informieren, dass Sie ihn beenden möchten. Dann kannst du eine haben select() Aufruf, der sowohl die Pipe als auch die Listening-Buchse auswählt.

Zum Beispiel (kompiliert, aber nicht vollständig getestet):

// 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 <unistd.h>
#include <assert.h>
#include <fcntl.h>

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);
}

Dann select mit receiverFd()und benachrichtigen Sie für die Beendigung mit notify().

  • Ich bin sehr neugierig darauf, habe Pfeifen nicht sehr oft benutzt. Haben Sie Code, den Sie posten könnten, um dies zu demonstrieren? 🙂

    – SamPost

    21. März 2010 um 13:25 Uhr

  • Vielen Dank. Ich habe tatsächlich über diesen Ansatz nachgedacht, als ich die Frage gepostet habe. Es ist sauber und funktioniert super.

    – selbe

    22. März 2010 um 1:16 Uhr

Benutzer-Avatar
Len Holgate

Schließen Sie den Listening-Socket und Accept gibt einen Fehler zurück.

Was funktioniert damit nicht zuverlässig? Beschreiben Sie die Probleme, mit denen Sie konfrontiert sind.

  • Das Aufrufen von close für einen Socket weckt keinen blockierenden Accept-Aufruf, der sich in einem anderen Thread befindet. Ich habe es unter Linux versucht – es funktioniert nicht. Ich glaube jedoch, dass diese Technik unter Windows funktioniert, was ich getan habe, um IOCP-Threads zum Aufwecken zu bringen.

    – selbe

    22. März 2010 um 1:17 Uhr

pthread_cancel zum Abbrechen eines in accept() blockierten Threads ist riskant, wenn die pthread-Implementierung das Abbrechen nicht richtig implementiert, das heißt, wenn der Thread einen Socket erstellt hat, kurz bevor Sie zu Ihrem Code zurückkehren, wird dafür ein pthread_cancel() aufgerufen, der Thread ist abgebrochen, und der neu erstellte Socket ist undicht. FreeBSD 9.0 und höher hat zwar kein solches Race-Condition-Problem, aber Sie sollten zuerst Ihr Betriebssystem überprüfen.

1365730cookie-checkReaktivierungs-Thread blockiert beim Aufruf von accept()

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy