In general, it is unsafe to make simultaneous calls to the same socket object 1 . The async_read() operation consists of zero or more intermediate async_read_some() . These intermediate operations are triggered only in threads that currently call io_service::run() . Internal threads are pretty transparent, and not one of the threads listed in the Platform Implementation Notes presents a problem.
Thus:
- If one thread calls
io_service::run() and socket.close() is called from the handler, then it is safe, since there is no possibility of simultaneous execution. The documentation refers to it as an implicit chain. - If one thread calls
io_service::run() and socket.close() is called from outside the handler, then it is unsafe since socket can have two simultaneous calls: close() from outside io_service and async_read_some() from the thread that is currently calling io_service::run() . To make it thread safe, send a handler to io_service that calls socket.close() . - If multiple threads call
io_service::run() , explicit strand is required to ensure thread safety. async_read() must be initiated from strand , and its completion handler must also be wrapped in the same chain. In addition, socket.close() must be sent through the chain.
For multi-level coroutines, using spawn() overload that accepts strand will execute the provided function in the context of strand . In addition, when a yield_context is passed as an asynchronous operation handler, handlers, including intermediate handlers from grouped operations, are called in the strand context. Therefore, to ensure thread safety, socket.close() should be:
called inside a coroutine:
// The lambda will execute within the context of my_strand. boost::asio::spawn(my_strand, [socket&](boost::asio::yield_context yield) { // In my_strand. // ... // The socket.async_read_some() operations that composed async_read() // will run within the context of my_strand. async_read(socket, ..., yield); // Still within my_strand. socket.close(); });
explicitly sent to my_strand :
// The lambda will execute within the context of my_strand. boost::asio::spawn(my_strand, [socket&](boost::asio::yield_context yield) { // In my_strand. // ... // The socket_.async_read_some() operations that composed async_read() // will run within the context of my_strand. async_read(socket, ..., yield); }); my_strand.dispatch([socket&](){ socket.close(); });
For more information on thread safety, compound operations, and chains, see the answer.
<sub> 1. The change history documents the anomaly of this rule. If supported by the OS, synchronous read, write, accept, and connect operations are thread safe. I include it here for completeness, but suggest using it with caution.
source share