Warum benötige ich bei boost::asio eine Litze pro Verbindung?

Lesezeit: 6 Minuten

Ich überprüfe HTTP-Server 3 Beispiel auf der Website von Boost.

Könntet ihr bitte erklären, warum ich brauche strand pro Verbindung? Wie ich sehe, rufen wir an read_some nur im Handler des Leseereignisses. Also im Wesentlichen read_some Aufrufe sind sequentiell, daher besteht keine Notwendigkeit für Strang (and Punkt 2 des 3. Absatzes sagt dasselbe). Wo liegt das Risiko in einer Multi-Threading-Umgebung?

  • “Bitte erklären Sie, warum ich Litze pro Verbindung brauche?”. Wo ist diese Empfehlung?

    – Nawaz

    18. August 2016 um 10:40 Uhr

Warum benotige ich bei boostasio eine Litze pro Verbindung
Gerber Sansbury

Die Dokumentation ist korrekt. Bei einer Halbduplex-Protokollimplementierung, wie z HTTP-Server 3der strand ist nicht nötig. Die Aufrufketten lassen sich wie folgt darstellen:

void connection::start()
{
  socket.async_receive_from(..., &handle_read);  ----.
}                                                    |
    .------------------------------------------------'
    |      .-----------------------------------------.
    V      V                                         |
void connection::handle_read(...)                    |
{                                                    |
  if (result)                                        |
    boost::asio::async_write(..., &handle_write); ---|--.
  else if (!result)                                  |  |
    boost::asio::async_write(..., &handle_write);  --|--|
  else                                               |  |
    socket_.async_read_some(..., &handle_read);  ----'  |
}                                                       |
    .---------------------------------------------------'
    |
    V
void handle_write(...)

Wie in der Abbildung gezeigt, wird pro Pfad nur ein einziges asynchrones Ereignis gestartet. Ohne Möglichkeit der gleichzeitigen Ausführung der Handler oder Operationen auf socket_soll es in einem impliziten Strang laufen.


Fadensicherheit

Obwohl es sich in dem Beispiel nicht als Problem darstellt, möchte ich ein wichtiges Detail von Strängen und zusammengesetzten Operationen hervorheben, wie z boost::asio::async_write. Bevor wir die Details erläutern, behandeln wir zunächst das Thread-Sicherheitsmodell mit Boost.Asio. Bei den meisten Boost.Asio-Objekten ist es sicher, dass mehrere asynchrone Operationen an einem Objekt anstehen; es wird lediglich angegeben, dass gleichzeitige Aufrufe des Objekts unsicher sind. In den folgenden Diagrammen stellt jede Spalte einen Thread dar und jede Zeile stellt dar, was ein Thread zu einem bestimmten Zeitpunkt tut.

Es ist sicher, dass ein einzelner Thread sequentielle Aufrufe macht, während andere Threads keine machen:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive(...);            | ...
socket.async_write_some(...);         | ...

Es ist sicher, dass mehrere Threads Aufrufe tätigen, aber nicht gleichzeitig:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive(...);            | ...
...                                   | socket.async_write_some(...);

Es ist jedoch nicht sicher, dass mehrere Threads gleichzeitig Aufrufe tätigen1:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive(...);            | socket.async_write_some(...);
...                                   | ...

Stränge

Um gleichzeitige Aufrufe zu verhindern, werden Handler oft innerhalb von Strängen aufgerufen. Dies geschieht entweder durch:

  • Wickeln Sie den Handler mit strand.wrap. Dadurch wird ein neuer Handler zurückgegeben, der durch den Strang versendet wird.
  • Buchung oder Versand direkt durch den Strang.

Zusammengesetzte Operationen sind insofern einzigartig, als Zwischenaufrufe an die Strom werden innerhalb der aufgerufen Handler‘s-Strang, falls einer vorhanden ist, anstelle des Strangs, in dem die zusammengesetzte Operation initiiert wird. Im Vergleich zu anderen Operationen stellt dies eine Umkehrung dessen dar, wo der Strang angegeben ist. Hier ist ein Beispielcode, der sich auf die Strangnutzung konzentriert und einen Socket demonstriert, aus dem über eine nicht zusammengesetzte Operation gelesen und gleichzeitig mit einer zusammengesetzten Operation geschrieben wird.

void start()
{
  // Start read and write chains.  If multiple threads have called run on
  // the service, then they may be running concurrently.  To protect the
  // socket, use the strand.
  strand_.post(&read);
  strand_.post(&write);
}

// read always needs to be posted through the strand because it invokes a
// non-composed operation on the socket.
void read()
{
  // async_receive is initiated from within the strand.  The handler does
  // not affect the strand in which async_receive is executed.
  socket_.async_receive(read_buffer_, &handle_read);
}

// This is not running within a strand, as read did not wrap it.
void handle_read()
{
  // Need to post read into the strand, otherwise the async_receive would
  // not be safe.
  strand_.post(&read);
}

// The entry into the write loop needs to be posted through a strand.
// All intermediate handlers and the next iteration of the asynchronous write
// loop will be running in a strand due to the handler being wrapped.
void write()
{
  // async_write will make one or more calls to socket_.async_write_some.
  // All intermediate handlers (calls after the first), are executed
  // within the handler's context (strand_).
  boost::asio::async_write(socket_, write_buffer_,
                           strand_.wrap(&handle_write));
}

// This will be invoked from within the strand, as it was a wrapped
// handler in write().
void handle_write()
{
  // handler_write() is invoked within a strand, so write() does not
  // have to dispatched through the strand.
  write();
}

Bedeutung von Handler-Typen

Auch innerhalb zusammengesetzter Operationen verwendet Boost.Asio argumentabhängige Suche (ADL) zum Aufrufen von Zwischenbehandlungsroutinen über den Strang der Vervollständigungsbehandlungsroutine. Daher ist es wichtig, dass der Typ des Completion-Handlers den passenden hat asio_handler_invoke() Haken. Wenn eine Typlöschung bei einem Typ auftritt, der nicht über die entsprechende asio_handler_invoke() Haken, wie z. B. ein Fall, in dem a boost::function wird aus dem Rückgabetyp von konstruiert strand.wrap, dann werden Zwischenhandler außerhalb des Strangs ausgeführt, und nur der Abschlusshandler wird innerhalb des Strangs ausgeführt. Weitere Informationen finden Sie in dieser Antwort.

Im folgenden Code werden alle Zwischenhandler und der Abschlusshandler innerhalb des Strangs ausgeführt:

boost::asio::async_write(stream, buffer, strand.wrap(&handle_write));

Im folgenden Code wird nur der Vervollständigungshandler innerhalb des Strangs ausgeführt. Keiner der Zwischenhandler wird innerhalb des Strangs ausgeführt:

boost::function<void()> handler(strand.wrap(&handle_write));
boost::asio::async_write(stream, buffer, handler);

1. Die Revisionsgeschichte dokumentiert eine Anomalie dieser Regel. Falls vom Betriebssystem unterstützt, synchron Lese-, Schreib-, Akzeptier- und Verbindungsvorgänge sind Thread-sicher. Ich füge es hier der Vollständigkeit halber hinzu, schlage jedoch vor, es mit Vorsicht zu verwenden.

  • @ruslan: Ich habe die Antwort aktualisiert, um hoffentlich mehr Klarheit und Details zu bieten. Um Ihre Fragen für diese Überarbeitung zu beantworten, hätten Sie write innerhalb des Strangs mit aufgerufen werden strand.post oder strand.dispatchwie mit strand.post( boost::bind( write ) ). Auch der strand_.wrap im zweiten Beispiel wäre nur sinnvoll, wenn handle_write verwendete Ressourcen, die synchronisiert werden müssen.

    – Gerber Sansbury

    10. Oktober 2012 um 16:09 Uhr

  • @Pubby: Bei beiden Beispielen wird die erste Operation innerhalb ausgeführt strand_onewährend alle Zwischenhandler und der Abschlusshandler innerhalb ausgeführt werden strand_two.

    – Gerber Sansbury

    29. April 2013 um 12:50 Uhr

  • Ich komme immer wieder auf diese Antwort zurück und verstehe neue, leicht subtile Einsichten. Dies ist eine bemerkenswert gut geschriebene Antwort.

    – sehen

    18. Juni 2015 um 14:25 Uhr

  • Die Antwort lautete: “Es ist jedoch nicht sicher, dass mehrere Threads gleichzeitig Aufrufe tätigen:”, das heißt, socket.async_receive(…); und socket.async_write_some(…); gleichzeitig ist unsicher. Warum ist es unsicher? Ich konnte die Beschreibung dazu im Boost.Asio-Dokument nicht finden. Ich verstehe, dass, wenn der Abschlusshandler des socket.async_receive(…); ruft socket.async_write_some(…) auf; , es ist wegen unsicher boost.org/doc/libs/1_65_1/doc/html/boost_asio/reference/… . Können Sie mir den Grund für die Ausführung von async_ nennen?[receive|write_some] selbst unsicher.

    – Takatoshi Kondo

    25. September 2017 um 5:20 Uhr

  • @TakatoshiKondo, es ist unsicher, weil die socket Dokumentation besagt, dass es unsicher ist, das Objekt gemeinsam zu verwenden. Die Übersicht über Threads und Boost.Asio kommentiert, dass es unsicher ist, die meisten Objekte gleichzeitig zu verwenden.

    – Gerber Sansbury

    25. September 2017 um 16:36 Uhr

Ich glaube, es liegt an der zusammengesetzten Operation async_write. async_write setzt sich aus mehreren zusammen socket::async_write_some asynchron. Strand ist hilfreich, um diese Operationen zu serialisieren. Chris Kohlhoff, der Autor von asio, spricht darüber kurz in seinem Boostcon-Gespräch um 1:17.

  • Der Link zu boostcon talk ist nicht mehr vorhanden.

    – John

    20. September 2021 um 1:08 Uhr

997590cookie-checkWarum benötige ich bei boost::asio eine Litze pro Verbindung?

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

Privacy policy