我使用boost.asio实现了一个TCP服务器,该服务器当前使用单个io_service
对象,我在该对象上从单个线程调用run
方法。
到目前为止,服务器能够立即回复客户端的请求,因为它在内存中有所有必要的信息(不需要在接收处理程序中长时间运行)。
现在需求已经改变,我需要从数据库中获取一些信息(使用ODBC) - 这基本上是一个长期运行的阻塞操作 - 以便为客户创建响应。
我看到了几种方法,但我不知道哪种方法最好(可能还有更多方法):
我可以在处理程序中保留长时间运行的操作,只需从多个线程调用io_service.run()
即可。我想我会使用尽可能多的线程,因为我有可用的CPU内核?
虽然这种方法很容易实现,但我不认为我会用这种方法获得最佳性能,因为线程数量有限(由于数据库访问更多是I / O,因此大多数时间都处于空闲状态绑定操作而不是计算绑定操作。)
在this document的第6部分中,它说:
将线程用于长时间运行的任务
单线程设计的一种变体,此设计仍然使用单个io_service :: run()线程来实现协议逻辑。长时间运行或阻塞任务被传递给后台线程,一旦完成,结果将被发送回io_service :: run()线程。
这听起来很有希望,但我不知道如何实现这一点。任何人都可以为这种方法提供一些代码片段/示例吗?
BorisSchäling在section 7.5 of his boost introduction中解释了如何使用自定义服务扩展boost.asio。
这看起来很多工作。与其他方法相比,这种方法有什么好处吗?
答案 0 :(得分:10)
这些方法并非明确相互排斥。我经常看到第一个和第二个的组合:
io_service
处理网络I / O. io_service
。此io_service
用作线程池,不会干扰处理网络I / O的线程。或者,每次需要长时间运行或阻塞任务时,可以生成一个分离的线程;但是,线程创建/销毁的开销可能会产生明显的影响。此answer提供线程池实现。此外,这是一个基本的例子,试图强调两个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
。
答案 1 :(得分:1)
我们的服务器(具有存储的传统协议)具有相同的长时间运行任务。所以我们的服务器运行200个线程以避免阻塞服务(是的,200个线程正在运行io_service::run
)。它不太好,但现在效果很好。
我们遇到的唯一问题是asio::strand
,它使用所谓的“实现”,当当前调用hadler时它会被锁定。解决了这个问题,通过io_service::post
来增加这股股票和“deattaching”任务,而没有包裹。
某些任务可能会运行几秒甚至几分钟,而且目前没有问题。