Home > Blockchain >  Does mulithreaded http processing with boost asio require strands?
Does mulithreaded http processing with boost asio require strands?

Time:03-14

In the boost asio documentation for strands it says:

Strands may be either implicit or explicit, as illustrated by the following alternative approaches:

  • ...
  • Where there is a single chain of asynchronous operations associated with a connection (e.g. in a half duplex protocol implementation like HTTP) there is no possibility of concurrent execution of the handlers. This is an implicit strand.
  • ...

However, in boost beast's example for a multithreaded asynchronous http server the boost::asio::ip::tcp::acceptor as well as each boost::asio::ip::tcp::socket get their own strand explicitly (see line 373 and 425). As far as I can see, this should not be necessary, since all of these objects are only ever going to be accessed in sequentially registered/running CompletionHandlers.¹ Precisely, a new async operation for one of these objects is only ever registered at the end of a CompletionHandler registered on the same object, making any object be used in a single chain of asynchronous operations

Thus, I'd assume that - despite of multiple threads running concurrently - strands could be omitted all together in this example and the io_context may be used for scheduling any async operation directly. Is that correct? If not, what issues of synchronization am I missing? Am I misunderstanding the statement in the documentation above?


¹: Of course, two sockets or a socket and the acceptor may be worked with concurrently but due to the use of multiple stands this is not prevented in the example either.

²: Admittedly, the CompletionHandler registered at the end of the current CompletionHandler may be started on another thread before the current handler actually finished, i. e. returns. But I would assume that this is not a circumstance risking synchronization problems. Correct me, if I am wrong.

CodePudding user response:

If the async chain of operations creates a logical strand, then often you don't need explicit strands.

Also, if the execution context is only ever run/polled from a single thread then all async operations will effective be on that implicit strand.

The examples serve more than one purpose.

  • On the one hand. they're obviously kept simple. Naturally there will be minimum number of threads or simplistic chains of operations.

  • However, that leads to over-simplified examples that have too little relation to real life.

  • Therefore, even if it's not absolutely required, the samples often show good practice or advanced patterns. Sometimes (often IME) this is even explicitly commented. E.g. in your very linked example L277:

     // We need to be executing within a strand to perform async operations
     // on the I/O objects in this session. Although not strictly necessary
     // for single-threaded contexts, this example code is written to be
     // thread-safe by default.
     net::dispatch(stream_.get_executor(),
                   beast::bind_front_handler(
                       &session::do_read,
                       shared_from_this()));
    

Motivational example

This allows people to solve their next non-trivial task. For example, imagine you wanted to add stop() to the listener class from the linked example. There's no way to do that safely without a strand. You would need to "inject" a call to acceptor.cancel() inside the logical "strand", the async operation chain containing async_accept. But you can't, because async_accept is "logically blocking" that chain. So you actually do need to post to an explicit strand:

void stop() {
  post(acceptor_.get_executor(), [this] { acceptor_.cancel(); });
}
  • Related