Was macht select(2), wenn Sie einen Dateideskriptor in einem separaten Thread schließen(2)?

Lesezeit: 5 Minuten

Benutzer-Avatar
Joe Shaw

Wie ist das Verhalten der select(2) Funktion, wenn ein Dateideskriptor, den es zum Lesen überwacht, von einem anderen Thread geschlossen wird?

Nach einigen flüchtigen Tests kehrt es sofort zurück. Ich vermute, das Ergebnis ist entweder, dass (a) es immer noch auf Daten wartet, aber wenn Sie tatsächlich versuchen würden, daraus zu lesen, würden Sie EBADF erhalten (möglicherweise – es gibt ein potenzielles Rennen) oder (b) dass es so tut, als ob der Dateideskriptor wurde nie übergeben. Wenn der letztere Fall zutrifft, würde die Übergabe eines einzelnen fd ohne Zeitüberschreitung einen Deadlock verursachen, wenn es geschlossen würde.

  • Mögliches Duplikat des Ausbruchs aus der Socket-Auswahl

    – iammilind

    4. Mai 2016 um 13:22 Uhr

  • Ich denke, es ist anders, wenn auch etwas verwandt. Die andere Frage stellt explizit die Frage, wie man aus a ausbricht select() aus einem anderen Thread (bzw pipe() ist eine gute Antwort), während es bei mir mehr um das Verhalten von ging close() auf einen select()Ed-Steckdose. In den Antworten unten sehen Sie, dass die Antwort “es kommt darauf an” lautet.

    – Joe Shaw

    10. Mai 2016 um 15:15 Uhr


  • Eine der mysteriöseren Bug-Jagden, die ich durchführte, war genau auf dieses Problem zurückzuführen: Thread A wählte Socket #x aus, der Thread B schloss. Kurz danach erstellte Thread C einen neuen Socket, der zufällig auch Socket #x war (weil der Netzwerkstack sich entschied, die Nummer x für den neuen Socket wiederzuverwenden). Zu diesem Zeitpunkt begann Thread A (der immer noch versuchte, Socket #x zu verwenden) natürlich mit dem Auswählen/Lesen/Schreiben von Daten auf dem Socket von Thread C, obwohl sie absolut keine logische Verbindung zueinander hatten. Das aufzuspüren war ein totaler Schmerz.

    – Jeremy Friesner

    20. Januar 2017 um 20:53 Uhr


Benutzer-Avatar
Joe Shaw

Aus einigen zusätzlichen Untersuchungen geht hervor, dass sowohl dwc als auch Bothie recht haben.

Bothies Antwort auf die Frage läuft darauf hinaus: Es ist undefiniertes Verhalten. Das bedeutet nicht, dass es unbedingt unvorhersehbar ist, aber dass verschiedene Betriebssysteme es unterschiedlich machen. Es scheint, dass Systeme wie Solaris und HP-UX zurückkehren select(2) in diesem Fall basiert Linux aber nicht auf diesen Beitrag an die Linux-Kernel-Mailingliste ab 2001.

Das Argument auf der Linux-Kernel-Mailingliste ist im Wesentlichen, dass es sich um ein undefiniertes (und fehlerhaftes) Verhalten handelt, auf das man sich verlassen kann. Im Fall von Linux das Aufrufen close(2) auf dem Dateideskriptor dekrementiert effektiv einen Referenzzähler darauf. Da es eine select(2) rufen Sie auch mit einem Verweis darauf auf, der fd bleibt geöffnet und wartet auf Eingaben, bis die select(2) kehrt zurück. Dies ist im Grunde die Antwort von dwc. Sie erhalten ein Ereignis für den Dateideskriptor und dann wird es geschlossen. Der Versuch, daraus zu lesen, führt zu einem EBADF, vorausgesetzt, die fd wurde nicht recycelt. (Eine Sorge, die MarkR in seiner Antwort geäußert hat, obwohl ich denke, dass sie in den meisten Fällen bei richtiger Synchronisierung wahrscheinlich vermeidbar ist.)

Also danke an alle für die Hilfe.

  • Es ist notwendigerweise unvorhersehbar. Es gibt keinen vorhersehbaren Weg close einen Dateideskriptor in einem Thread, während ein anderer Thread ihn zum Einlesen überwacht select weil es auf keinen fall den thread aufruft close wissen, ob der andere Thread bereits blockiert ist oder nicht select oder im Begriff zu blockieren select.

    – David Schwartz

    20. Januar 2017 um 19:44 Uhr

Ich würde erwarten, dass es sich so verhalten würde, als wäre das Dateiende erreicht worden, das heißt, es würde mit dem als bereit angezeigten Dateideskriptor zurückkehren, aber jeder Versuch, es anschließend zu lesen, würde “schlechter Dateideskriptor” zurückgeben.

Allerdings ist dies sowieso eine sehr schlechte Übung, da Sie immer potenzielle Rennbedingungen haben würden Ein weiterer Dateideskriptor mit der gleichen Nummer könnte sofort von einem anderen Thread geöffnet werden, nachdem der andere 2. ihn geschlossen hat, dann würde der ausgewählte Thread am Ende auf den falschen warten.

Sobald Sie eine Datei schließen, wird ihre Nummer zur Wiederverwendung verfügbar und kann beim nächsten Aufruf von open(), socket() usw. wiederverwendet werden, selbst wenn von einem anderen Thread. Deshalb müssen Sie solche Dinge wirklich, wirklich vermeiden.

  • Ich dachte, dass es auch als bereit zurückkehren könnte, aber das ist nicht ganz richtig: Der Deskriptor befindet sich eigentlich nicht in einem bereiten Zustand – er ist geschlossen. Und wie Sie bereits erwähnt haben, könnte es zu dem Zeitpunkt, an dem Sie es verwenden, etwas anderem zugewiesen werden.

    – Joe Shaw

    12. Februar 2009 um 22:07 Uhr

  • Sie könnten das Rennen jedoch vermeiden, indem Sie einen Mutex für die Datenstruktur verwenden, die das fd enthält. Aber das würde nur funktionieren, wenn für den Aufruf von select() ein Timeout definiert wäre.

    – Joe Shaw

    13. Februar 2009 um 16:41 Uhr

Der Select-Systemaufruf ist eine Möglichkeit, darauf zu warten, dass Dateideskriptoren den Status ändern, während die Programme nichts anderes zu tun haben. Die Hauptanwendung sind Serveranwendungen, die eine Reihe von Dateideskriptoren öffnen und dann darauf warten, dass etwas mit ihnen geschieht (neue Verbindungen akzeptieren, Anforderungen lesen oder Antworten senden). Diese Dateideskriptoren werden im nicht blockierenden IO-Modus geöffnet, sodass der Serverprozess zu keinem Zeitpunkt in einem Systemaufruf hängen bleibt.

Dies bedeutet außerdem, dass keine separaten Threads erforderlich sind, da die gesamte Arbeit, die im Thread erledigt werden könnte, auch vor dem Select-Aufruf erledigt werden kann. Und wenn die Arbeit lange dauert, dann kann sie unterbrochen werden, Aufruf mit timeout={0,0} auswählen, die Dateideskriptoren werden verarbeitet und danach wird die Arbeit wieder aufgenommen.

Jetzt schließen Sie einen Dateideskriptor in einem anderen Thread. Warum haben Sie überhaupt diesen zusätzlichen Thread und warum soll er den Dateideskriptor schließen?

Der POSIX-Standard gibt keine Hinweise, was in diesem Fall passiert, Sie machen also UNDEFINED BEHAVIOR. Erwarten Sie, dass das Ergebnis zwischen verschiedenen Betriebssystemen und sogar zwischen Versionen desselben Betriebssystems sehr unterschiedlich sein wird.

Gruß Bodo

  • Ich denke, es wird sowieso ein undefiniertes Verhalten haben, da es unmöglich ist, die Race-Bedingung des geschlossenen Dateideskriptors zu entfernen kurz bevor die Auswahl und eine andere, die mit derselben Nummer geöffnet wird.

    – MarkR

    13. Februar 2009 um 16:32 Uhr

Es ist ein wenig verwirrend, was Sie fragen …

Select() sollte bei einer “interessanten” Änderung zurückkehren. Wenn close() lediglich die Referenzzählung verringert hat und die Datei noch irgendwo zum Schreiben geöffnet war, gibt es keinen Grund für select() aufzuwachen.

Wenn der andere Thread close() auf dem einzigen offenen Deskriptor ausgeführt hat, wird es interessanter, aber ich müsste eine einfache Version des Codes sehen, um zu sehen, ob etwas wirklich falsch ist.

1363850cookie-checkWas macht select(2), wenn Sie einen Dateideskriptor in einem separaten Thread schließen(2)?

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

Privacy policy