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:
#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