Wie kann ich select() signalisieren, sofort zurückzukehren?

Lesezeit: 6 Minuten

Ich habe einen Worker-Thread, der einen TCP-Socket auf eingehenden Datenverkehr überwacht und die empfangenen Daten puffert, damit der Haupt-Thread darauf zugreifen kann (nennen wir diesen Socket EIN). Der Worker-Thread muss jedoch auch einige reguläre Operationen ausführen (z. B. einmal pro Sekunde), auch wenn keine Daten eingehen. Daher verwende ich select() mit einem Timeout, damit ich nicht weiter abfragen muss. (Beachten Sie das Aufrufen receive() auf einem nicht blockierenden Socket und dann für eine Sekunde schlafen, ist nicht gut: Die eingehenden Daten sollten sofort für den Haupt-Thread verfügbar sein, auch wenn der Haupt-Thread sie möglicherweise nicht immer sofort verarbeiten kann, daher die Notwendigkeit einer Pufferung. )

Jetzt muss ich auch in der Lage sein, dem Worker-Thread zu signalisieren, sofort etwas anderes zu tun; aus dem Hauptthread muss ich den Arbeitsthread machen select() gleich wieder zurück. Ich habe das vorerst wie folgt gelöst (Ansatz grundsätzlich übernommen von hier und hier):

Beim Programmstart erstellt der Worker-Thread zu diesem Zweck einen zusätzlichen Socket vom Typ Datagramm (UDP) und bindet ihn an einen zufälligen Port (nennen wir diesen Socket B). Ebenso erstellt der Haupt-Thread einen Datagramm-Socket zum Senden. In seinem Aufruf an select()listet der Worker-Thread jetzt beide auf EIN und B in dem fd_set. Wenn der Haupt-Thread signalisieren muss, ist es sendto()‘s ein paar Bytes an den entsprechenden Port weiter localhost. Zurück im Worker-Thread, wenn B bleibt im fd_set nach select() kehrt dann zurück recvfrom() aufgerufen und die empfangenen Bytes werden einfach ignoriert.

Das scheint sehr gut zu funktionieren, aber ich kann nicht sagen, dass mir die Lösung gefällt, hauptsächlich, weil dafür ein zusätzlicher Port gebunden werden muss Bund auch, weil es mehrere zusätzliche Socket-API-Aufrufe hinzufügt, die meiner Meinung nach fehlschlagen können – und ich habe nicht wirklich Lust, die geeignete Aktion für jeden der Fälle herauszufinden.

Ich denke, idealerweise möchte ich irgendeine Funktion aufrufen, die dauert EIN als Eingabe und tut nichts außer macht select() gleich wieder zurück. Allerdings kenne ich eine solche Funktion nicht. (Ich denke, ich könnte z shutdown() die Steckdose, aber die Nebenwirkungen sind nicht wirklich akzeptabel 🙂

Wenn dies nicht möglich ist, wäre die zweitbeste Option die Erstellung einer B Das ist viel dummer als ein echter UDP-Socket und erfordert nicht wirklich die Zuweisung begrenzter Ressourcen (über eine angemessene Menge an Speicher hinaus). ich vermute Unix-Domain-Sockets würde genau das tun, aber: Die Lösung sollte nicht viel weniger plattformübergreifend sein als das, was ich derzeit habe, wenn auch etwas moderater #ifdef das Zeug ist in Ordnung. (Ich ziele hauptsächlich auf Windows und Linux ab – und schreibe nebenbei C++.)

Bitte schlagen Sie kein Refactoring vor, um die beiden separaten Threads loszuwerden. Dieses Design ist notwendig, da der Haupt-Thread möglicherweise für längere Zeit blockiert ist (z. B. durch intensive Berechnungen – und ich kann nicht regelmäßig aufrufen receive() aus der innersten Rechenschleife), und in der Zwischenzeit muss jemand die eingehenden Daten puffern (und aus Gründen, die ich nicht beeinflussen kann, kann es nicht der Absender sein).

Jetzt, wo ich dies schreibe, ist mir klar geworden, dass jemand definitiv einfach antworten wird “Boost.Asio“, also habe ich es mir gerade zum ersten Mal angesehen … Ich konnte jedoch keine offensichtliche Lösung finden. Beachten Sie, dass ich auch nicht (leicht) beeinflussen kann, wie die Steckdose funktioniert EIN erstellt wird, aber ich sollte in der Lage sein, es bei Bedarf von anderen Objekten umhüllen zu lassen.

Benutzer-Avatar
Alex B

Du bist fast am Ziel. Verwenden ein „Self-Pipe“-Trick. Öffnen Sie eine Pipe, fügen Sie sie Ihrer hinzu select() lesen und Schreiben fd_set, schreiben Sie vom Haupt-Thread darauf, um einen Worker-Thread zu entsperren. Es ist über POSIX-Systeme hinweg portierbar.

Ich habe eine Variante einer ähnlichen Technik für Windows in einem System gesehen (tatsächlich zusammen mit der obigen Methode verwendet, getrennt durch #ifdef WIN32). Das Entsperren kann durch Hinzufügen eines Dummy- (ungebundenen) Datagramm-Sockets erreicht werden fd_set und schließt es dann. Der Nachteil ist, dass Sie es natürlich jedes Mal neu öffnen müssen.

In dem oben erwähnten System werden diese beiden Verfahren jedoch ziemlich sparsam und für unerwartete Ereignisse (z. B. Signale, Beendigungsanforderungen) verwendet. Bevorzugte Methode ist immer noch ein variables Timeout dazu select()abhängig davon, wie bald etwas für einen Worker-Thread geplant ist.

  • Danke, aber wie in der Frage angegeben, sollte die endgültige Lösung auch unter Windows funktionieren.

    – Reunanen

    21. Dezember 2008 um 12:31 Uhr

  • Ok, danke für den Windows-Vorschlag. Haben Sie eine Ahnung, wie teuer die Wiedereröffnung in der Praxis ist?

    – Reunanen

    21. Dezember 2008 um 12:48 Uhr

  • Danke Checkers, das Erstellen und Schließen des (nicht gebundenen) Sockets scheint tatsächlich sehr gut zu funktionieren.

    – Reunanen

    21. Dezember 2008 um 15:21 Uhr

  • Ich habe keine harten Zahlen, aber da Sie für Windows schreiben, bedeutet es fast immer Desktop, was bedeutet, dass die Kosten vernachlässigbar sein sollten.

    – Alex B

    22. Dezember 2008 um 7:30 Uhr

  • Unter Windows können Sie auch einfach einen UDP-Socket offen halten und ein Paket an ihn senden, damit select() auf diesem Socket lesebereit zurückkehrt, oder Sie können ein Paar TCP-Sockets erstellen, die miteinander verbunden sind, und eine senden Byte auf einem von ihnen während select() überwacht den anderen.

    – Jeremy Friesner

    29. Dezember 2018 um 0:54 Uhr

Die Verwendung einer Pipe anstelle eines Sockets ist etwas sauberer, da es keine Möglichkeit für einen anderen Prozess gibt, daran zu kommen und die Dinge durcheinander zu bringen.

Die Verwendung eines UDP-Sockets schafft definitiv die Möglichkeit, dass Streupakete hereinkommen und stören.

Eine anonyme Pipe steht keinem anderen Prozess zur Verfügung (es sei denn, Sie geben sie ihm).

Sie könnten auch Signale verwenden, aber in einem Multithread-Programm sollten Sie sicherstellen, dass alle Threads mit Ausnahme des gewünschten Threads dieses Signal maskiert haben.

Unter Unix ist es einfach, eine Pipe zu verwenden. Wenn Sie unter Windows arbeiten und weiterhin die select-Anweisung verwenden möchten, um Ihren Code mit Unix kompatibel zu halten, funktioniert der Trick, einen ungebundenen UDP-Socket zu erstellen und ihn zu schließen, gut und einfach. Aber Sie müssen es multi-threadsafe machen.

Die einzige Möglichkeit, die ich gefunden habe, um dies multithreadsicher zu machen, besteht darin, den Socket im selben Thread zu schließen und neu zu erstellen, in dem die select-Anweisung ausgeführt wird. Das ist natürlich schwierig, wenn der Thread bei der Auswahl blockiert. Und dann kommt im Windows der Aufruf QueueUserAPC. Wenn Windows in der Select-Anweisung blockiert, kann der Thread asynchrone Prozeduraufrufe verarbeiten. Sie können dies mithilfe von QueueUserAPC von einem anderen Thread aus planen. Windows unterbricht die Auswahl, führt Ihre Funktion im selben Thread aus und fährt mit der Auswahlanweisung fort. Sie können jetzt in Ihrer APC-Methode den Socket schließen und neu erstellen. Garantiert Thread-sicher und Sie werden nie ein Signal verlieren.

Benutzer-Avatar
Bruce Lu

Um es einfach zu machen: Eine globale Variable speichert das Socket-Handle und schließt dann den globalen Socket, die select() wird sofort zurückkommen: closesocket(g_socket);

1371070cookie-checkWie kann ich select() signalisieren, sofort zurückzukehren?

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

Privacy policy