boost asio处理程序中的长时间运行/阻止操作 [英] Long-running / blocking operations in boost asio handlers

查看:72
本文介绍了boost asio处理程序中的长时间运行/阻止操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用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. This io_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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆