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?
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.
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.
“Bitte erklären Sie, warum ich Litze pro Verbindung brauche?”. Wo ist diese Empfehlung?
– Nawaz
18. August 2016 um 10:40 Uhr