boost asio处理程序中的长时间运行/阻止操作 [英] Long-running / blocking operations in boost asio handlers
问题描述
我使用boost.asio实现了一个TCP服务器,该服务器当前使用单个io_service
对象,在该对象上我从单个线程调用run
方法.
I implemented a TCP server using boost.asio which currently uses a single io_service
object on which I call the run
method from a single thread.
到目前为止,由于服务器在内存中具有所有必要的信息,因此服务器能够立即响应客户端的请求(无需在接收处理程序中长时间运行操作).
So far the server was able to answer the requests of the clients immediately, since it had all necessary information in the memory (no long-running operations in the receive handler were necessary).
现在的需求已经改变,我需要从数据库中获取一些信息(使用ODBC)(这基本上是一个长期运行的阻塞操作),以便为客户端创建响应.
Now requirements have changed and I need to get some information out of a database (with ODBC) - which is basically a long-running blocking operation - in order to create the response for the clients.
我看到了几种方法,但是我不知道哪种方法最好(而且可能还有更多的方法):
I see several approaches, but I don't know which one is best (and there are probably even more approaches):
我可以将长时间运行的操作保留在处理程序中,只需从多个线程调用io_service.run()
即可.我想我会使用尽可能多的线程,因为我有可用的CPU内核?
I could keep the long running operations in the handlers, and simply call io_service.run()
from multiple threads. I guess I would use as many threads as I have CPU cores available?
虽然这种方法很容易实现,但我认为由于线程数量有限(由于数据库访问更多是I/O,所以大多数时间都处于空闲状态)不会使这种方法获得最佳性能绑定操作而不是计算绑定操作).
While this approach would be easy to implement, I don't think I would get the best performance with this approach because of the limited number of threads (which are idling most of the time since database access is more an I/O-bound operation than a compute-bound operation).
此文档的第6部分它说:
将线程用于长时间运行的任务
Use threads for long running tasks
单线程设计的一种变体,该设计仍使用单个io_service :: run()线程来实现协议逻辑.长时间运行或阻塞的任务将传递到后台线程,完成后,结果将回传到io_service :: run()线程.
A variant of the single-threaded design, this design still uses a single io_service::run() thread for implementing protocol logic. Long running or blocking tasks are passed to a background thread and, once completed, the result is posted back to the io_service::run() thread.
这听起来很有希望,但是我不知道如何实现.谁能为此方法提供一些代码片段/示例?
This sounds promising, but I don't know how to implement that. Can anyone provide some code snippet / example for this approach?
鲍里斯·舍林(BorisSchäling)在第7.5节介绍中介绍了如何通过自定义服务扩展boost.asio.
Boris Schäling explains in section 7.5 of his boost introduction how to extend boost.asio with custom services.
这看起来像很多工作.与其他方法相比,这种方法有什么好处吗?
This looks like a lot of work. Does this approach have any benefits compared to the other approaches?
推荐答案
这些方法不是明确相互排斥的.我经常看到第一个和第二个的组合:
The approaches are not explicitly mutually exclusive. I often see a combination of the first and second:
- 一个或多个线程正在一个
io_service
中处理网络I/O. - 长时间运行或阻止的任务会发布到其他
io_service
中.此io_service
用作线程池,不会干扰处理网络I/O的线程.另外,每次需要长时间运行或阻塞的任务时,都可以生成一个分离的线程.但是,线程创建/销毁的开销可能会产生明显的影响.
- One or more thread are processing network I/O in one
io_service
. - Long running or blocking tasks are posted into a different
io_service
. Thisio_service
functions as a thread pool that will not interfere with threads handling network I/O. Alternatively, one could spawn a detached thread every time a long running or blocking task is needed; however, the overhead of thread creation/destruction may a noticeable impact.
此 answer 提供了线程池实现.此外,这是一个基本示例,试图强调两个io_services
之间的交互.
This answer that provides a thread pool implementation. Additionally, here is a basic example that tries to emphasize the interaction between two io_services
.
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/optional.hpp>
#include <boost/thread.hpp>
/// @brief Background service will function as a thread-pool where
/// long-standing blocking operations may occur without affecting
/// the network event loop.
boost::asio::io_service background_service;
/// @brief The main io_service will handle network operations.
boost::asio::io_service io_service;
boost::optional<boost::asio::io_service::work> work;
/// @brief ODBC blocking operation.
///
/// @brief data Data to use for query.
/// @brief handler Handler to invoke upon completion of operation.
template <typename Handler>
void query_odbc(unsigned int data,
Handler handler)
{
std::cout << "in background service, start querying odbc\n";
std::cout.flush();
// Mimic busy work.
boost::this_thread::sleep_for(boost::chrono::seconds(5));
std::cout << "in background service, posting odbc result to main service\n";
std::cout.flush();
io_service.post(boost::bind(handler, data * 2));
}
/// @brief Functions as a continuation for handle_read, that will be
/// invoked with results from ODBC.
void handle_read_odbc(unsigned int result)
{
std::stringstream stream;
stream << "in main service, got " << result << " from odbc.\n";
std::cout << stream.str();
std::cout.flush();
// Allow io_service to stop in this example.
work = boost::none;
}
/// @brief Mocked up read handler that will post work into a background
/// service.
void handle_read(const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "in main service, need to query odbc" << std::endl;
typedef void (*handler_type)(unsigned int);
background_service.post(boost::bind(&query_odbc<handler_type>,
21, // data
&handle_read_odbc) // handler
);
// Keep io_service event loop running in this example.
work = boost::in_place(boost::ref(io_service));
}
/// @brief Loop to show concurrency.
void print_loop(unsigned int iteration)
{
if (!iteration) return;
std::cout << " in main service, doing work.\n";
std::cout.flush();
boost::this_thread::sleep_for(boost::chrono::seconds(1));
io_service.post(boost::bind(&print_loop, --iteration));
}
int main()
{
boost::optional<boost::asio::io_service::work> background_work(
boost::in_place(boost::ref(background_service)));
// Dedicate 3 threads to performing long-standing blocking operations.
boost::thread_group background_threads;
for (std::size_t i = 0; i < 3; ++i)
background_threads.create_thread(
boost::bind(&boost::asio::io_service::run, &background_service));
// Post a mocked up 'handle read' handler into the main io_service.
io_service.post(boost::bind(&handle_read,
make_error_code(boost::system::errc::success), 0));
// Post a mockup loop into the io_service to show concurrency.
io_service.post(boost::bind(&print_loop, 5));
// Run the main io_service.
io_service.run();
// Cleanup background.
background_work = boost::none;
background_threads.join_all();
}
输出:
in main service, need to query odbc
in main service, doing work.
in background service, start querying odbc
in main service, doing work.
in main service, doing work.
in main service, doing work.
in main service, doing work.
in background service, posting odbc result to main service
in main service, got 42 from odbc.
请注意,处理主io_service
帖子的单线程工作到background_service
,然后在background_service
阻止时继续处理其事件循环. background_service
获得结果后,会将处理程序发布到主io_service
中.
Note that the single thread processing the main io_service
posts work into the background_service
, and then continues to process its event loop while the background_service
blocks. Once the background_service
gets a result, it posts a handler into the main io_service
.
这篇关于boost asio处理程序中的长时间运行/阻止操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!