-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using asio::io_context in single threaded applications #15
Comments
Hi, I am glad to hear that my library is helpful. When I started writing the library I tried doing something like: while(!io_context.stopped() && !grpc_context.stopped()) {
io_context.run_one();
grpc_context.run_one();
} but the performance was very bad. I could try it again and see whether I can speed it up. I will put that on the agenda for v1.5.0. If you have multiple execution contexts you could try to declare one of them as the "main" context where all your business logic runs. Let's assume you have created a tcp::socket with an io_context: asio::co_spawn(
grpc_context,
[&]() -> asio::awaitable<void> {
// ... some business logic that will be performed in the thread of the grpc_context
// Interaction with the io_context is thread-safe as long as you do not use one of the
// concurrency hints.
co_await socket.async_wait(asio::ip::tcp::socket::wait_read, asio::use_awaitable);
// async_wait will automatically dispatch back to the grpc_context when it completes
// ... some more business logic that will be performed in the thread of the grpc_context
// It is also possible to explicitly switch to the grpc_context. By using asio::dispatch
// it will be a no-op if we already are on the grpc_context.
co_await asio::dispatch(asio::bind_executor(grpc_context, asio::use_awaitable));
} I have recently added an example that uses grpc_context and io_context, maybe that can give you some more ideas: file-transfer-client and file-transfer-server |
Thanks for your response, |
grpc::CompletionQueue is already an event loop. Some thread must repeatedly invoke its asio::defer(io_context, [&] {
grpc_context.poll();
asio::defer(io_context, [&] {
grpc_context.poll();
// recursive
}
}; Like I said, I will try it out and see what can be done. |
I have implemented the above mentioned asio::defer style code on a separate branch: https://github.com/Tradias/asio-grpc/tree/grpc_context-poll Still needs some more thought on the API desgin. Usage for now is: #include <agrpc/pollContext.hpp>
// Must stay alive until grpc_context stops
agrpc::PollContext context{io_context.get_executor()};
context.poll(grpc_context);
io_context.run(); Performance on an otherwise idle io_context seems to be almost identical to running GrpcContext on its own thread, which is great news.
|
Thanks for your work, |
Hi, thanks for sharing your awesome solution with us! It looks like the snippet below is exactly what I need - I'm working on a project that requires both grpc and REST-like API at the same time - imagine that I'm accepting REST for authorization and use given credentials for executing GRPC calls. Am I correct that i may use this approach for running a coroutine that executes grpc call? At the moment I plan to use something like grpc-gateway, but I'm considering other options.
|
Correct I assume in your case you would want the io_context to be the "main" context. In that case all you need to change is: asio::co_spawn(
io_context,
[&]() -> asio::awaitable<void> {
// Some REST API logic:
co_await socket.async_wait(asio::ip::tcp::socket::wait_read, asio::use_awaitable);
// A client streaming RPC, just an example
grpc::ClientContext client_context;
example::v1::Response response;
std::unique_ptr<grpc::ClientAsyncWriter<example::v1::Request>> writer;
// Must use asio::bind_executor because asio::this_coro::executor does not refer to a GrpcExecutor
co_await agrpc::request(&example::v1::Example::Stub::AsyncClientStreaming, stub, client_context,
writer, response, asio::bind_executor(grpc_context, asio::use_awaitable));
// Now executing in the thread that called grpc_context.run(). We can still interact with the asio IoObjects,
// like the socket, from here since they are thread-safe (unless you have set certain concurrency hints).
} And then run the io_context and grpc_context: agrpc::GrpcContext grpc_context{std::make_unique<grpc::CompletionQueue>()};
auto guard = asio::make_work_guard(grpc_context);
asio::io_context io_context{1};
asio::co_spawn(io_context, ...);
std::thread grpc_context_thread{[&] { grpc_context.run(); }};
io_context.run();
guard.reset();
grpc_context_thread.join(); |
Will it support io_context with thread pool enabled(io_context.run() called from multiple threads) ? |
@vangork yes it should, although I would expect the performance to be slightly worse than running it on a single thread. |
I pushed a client and server example showing how to run io_context and grpc_context in the same thread with the new PollContext. Here are some performance numbers from my machine:
I used grpc_bench to load the grpc_context and repeated calls to Another important thing to note is that the PollContext will bring CPU consumption of the shared thread to 100% even while io_context and grpc_context are idle. |
Hi,
Thank you for your awesome library.
It is a very convenient way to use asio for writing single threaded (but concurrent) applications without worrying about problems in multi threaded applications.
If i'm not mistaken, right now the only way to use agrpc is to instantiate GrpcContext and run it on its own thread, which means we need to run asio::io_context on a separate thread and deal with concurrency problems between them.
Is there any plan for making it possible to reuse asio::io_context for agrpc services?
The text was updated successfully, but these errors were encountered: