Home > Software design >  Is it safe to disable threads on boost::asio in a multi-threaded program?
Is it safe to disable threads on boost::asio in a multi-threaded program?

Time:05-19

I read in this SO answer that there are locks around several parts of asio's internals.

In addition I'm aware that asio is designed to allow multiple threads to service a single io_context.

However, if I only have a single thread servicing a single io_context, but I want to have more than 1 io_context in my application, is it safe to disable threads (per BOOST_ASIO_DISABLE_THREADS)

That is: I have one io_context and one thread which has entered its io_context::run() loop, and it is servicing a number of sockets etc. All interaction with these sockets are done within the context of that thread.

I then also have another thread, and another io_context, and that thread services that io_context and its sockets etc.

Inter-thread communication is achieved using a custom thread-safe queue and an eventfd wrapped with an asio::posix::stream_descriptor which is written to by the initiating thread, and read from the receiving thread which then pops items off the thread-safe queue.

So at no point will there be user code which attempts to call asio functions from a thread which isn't associated with the io_context servicing its asio objects.

With the above use-case in mind, is it safe to disable threads in asio?

CodePudding user response:

It'll depend. As far as I know it is ought to be fine. See below for caveats/areas of attention.

Also, you might want to take a step back and think about the objectives. If you're trying to optimize areas containing async IO, there may be quick wins that don't require such drastic measures. That is not to say that there are certainly situations where I imagine BOOST_ASIO_DISABLE_THREADS will help squeeze just that little extra bit of performance out.


Impact

What BOOST_ASIO_DISABLE_THREADS does is

  • replace selected mutexes/events with null implementations
  • disable some internal thread support (boost::asio::detail::thread throws on construction)
  • removes atomics (atomic_count becomes non-atomic)
  • make globals behave as simple statics (applies to system_context/system_executor)
  • disables TLS support

System executor

It's worth noting that system_executor is the default fallback when querying for associated handler executors. The library implementation specifies that async initiations will override that default with the executor of any IO object involved (e.g. the one bound to your socket or timer).

However, you have to scrutinize your own use and that of third-party code to make sure you don't accidentally rely on fallback.

Update: turns out system_executor internally spawns a thread_group which uses detail::thread - correctly erroring out when used

IO Services

Asio is extensible. Some services may elect to run internal threads as an implementation detail.

docs:

The implementation of this library for a particular platform may make use of one or more internal threads to emulate asynchronicity. As far as possible, these threads must be invisible to the library user. [...]

I'd trust the library implementation to use detail::thread - causing a runtime error if that were to be the case.

However, again, when using third-party code/user services you'll have to make sure that they don't break your assumptions.

Also, specific operations will not work without the thread support, like:

Live On Coliru

#define BOOST_ASIO_DISABLE_THREADS
#include <boost/asio.hpp>
#include <iostream>

int main() {
    boost::asio::io_context ioc;
    boost::asio::ip::tcp::resolver r{ioc};

    std::cout << r.resolve("127.0.0.1", "80")->endpoint() << std::endl; // fine

    // throws "thread: not supported":
    r.async_resolve("127.0.0.1", "80", [](auto...) {});
}

Prints

127.0.0.1:80
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  thread: Operation not supported [system:95]
bash: line 7: 25771 Aborted                 (core dumped) ./a.out
  • Related