我正考虑在我们目前使用的现有专有第三方网络协议之上编写自定义Asio服务。
根据Highscore Asio guide,您需要实现三个类来创建自定义Asio服务:
boost::asio::basic_io_object
派生的类,表示新的I / O对象。boost::asio::io_service::service
的类,表示向I / O服务注册的服务,可以从I / O对象访问。网络协议实现已经提供了异步操作,并且具有(阻塞)事件循环。所以我想,我会把它放到我的服务实现类中,并在内部工作线程中运行事件循环。到目前为止一切都很好。
查看自定义服务的一些示例,我注意到服务类产生了自己的内部线程(实际上它们实例化了自己的内部io_service实例)。例如:
Highscore页面提供directory monitor example。它本质上是inotify的包装器。有趣的课程是inotify/basic_dir_monitor_service.hpp
和inotify/dir_monitor_impl.hpp
。 Dir_monitor_impl
处理与inofity的实际交互,inofity是阻塞的,因此在后台线程中运行。我同意这一点。但是basic_dir_monitor_service
也有一个内部工作线程,所有似乎正在做的就是在主要io_service
和dir_monitor_impl
之间改变请求。我玩了代码,删除了basic_dir_monitor_service
中的工作线程,而是直接将请求发布到主io_service,程序仍像以前一样运行。
在Asio的custom logger service example中,我注意到了同样的方法。 logger_service
生成一个内部工作线程来处理日志记录请求。我没有时间玩这个代码,但我认为,应该可以将这些请求直接发布到主io_service。
拥有这些"中介员工的优势是什么?你不能一直把所有的工作都发布到主io_service吗?我是否错过了Proactor模式的一些重要方面?
我应该提一下,我正在为功能不足的单核嵌入式系统编写软件。如果有这些额外的线程,似乎就会施加不必要的上下文切换,如果可能的话,我希望避免这种切换。
答案 0 :(得分:27)
简而言之,一致性。这些服务试图满足Boost.Asio提供的服务所规定的用户期望。
使用内部io_service
可明确区分所有权和处理程序的控制权。如果自定义服务将其内部处理程序发布到用户的io_service
中,则服务的内部处理程序的执行将与用户的处理程序隐式耦合。考虑一下这会如何影响用户对Boost.Asio Logger Service示例的期望:
logger_service
在处理程序中写入文件流。因此,永远不会处理io_service
事件循环的程序(例如仅使用同步API的程序)永远不会写入日志消息。logger_service
由多个线程处理,io_service
将不再是线程安全的,可能会调用未定义的行为。logger_service
内部操作的生命周期受io_service
的限制。例如,当调用服务的shutdown_service()
函数时,拥有io_service
的生命周期已经结束。因此,无法在logger_service::log()
内通过shutdown_service()
记录消息,因为它会尝试将内部处理程序发布到其生命周期已经结束的io_service
。用户可能不再假设操作和处理程序之间的一对一映射。例如:
boost::asio::io_service io_service;
debug_stream_socket socket(io_service);
boost::asio::async_connect(socket, ..., &connect_handler);
io_service.poll();
// Can no longer assume connect_handler has been invoked.
在这种情况下,io_service.poll()
可以调用logger_service
内部的处理程序,而不是connect_handler()
。
此外,这些内部线程试图模仿Boost.Asio itself内部使用的行为:
针对特定平台实现此库可能会使用一个或多个内部线程来模拟异步性。这些线程必须尽可能对库用户不可见。
在目录监视器示例中,内部线程用于防止在等待事件时无限期阻止用户io_service
。一旦发生事件,就可以调用完成处理程序,因此内部线程将用户处理程序发布到用户的io_service
以进行延迟调用。此实现使用对用户来说几乎不可见的内部线程模拟异步性。
有关详细信息,当通过dir_monitor::async_monitor()
启动异步监视器操作时,basic_dir_monitor_service::monitor_operation
将发布到内部io_service
。调用时,此操作会调用dir_monitor_impl::popfront_event()
,这是一个可能阻塞的调用。因此,如果将monitor_operation
发布到用户的io_service
中,则可以无限期阻止用户的线程。考虑对以下代码的影响:
boost::asio::io_service io_service;
boost::asio::dir_monitor dir_monitor(io_service);
dir_monitor.add_directory(dir_name);
// Post monitor_operation into io_service.
dir_monitor.async_monitor(...);
io_service.post(&user_handler);
io_service.run();
在上面的代码中,如果io_service.run()
首先调用monitor_operation
,则在user_handler()
观察dir_monitor
目录上的事件之前,不会调用dir_name
。因此,dir_monitor
服务的实现不会以大多数用户期望的其他服务的一致方式运行。
使用内部线程和io_service
:
std::ofstream
的线程安全性,因为只有单个内部线程写入流。如果直接在logger_service::log()
内完成日志记录,或者logger_service
将其处理程序发布到用户的io_service
中,则线程安全需要显式同步。其他同步机制可能会在实现中引入更大的开销或复杂性。允许services
在shutdown_service()
内记录消息。在destruction期间,io_service
会:
io_service
或其任何关联的strand
中计划延期调用的所有未经调用的处理程序。
由于用户io_service
的生命周期已结束,其事件队列既未被处理也无法发布其他处理程序。通过拥有由其自己的线程处理的内部io_service
,logger_service
允许其他服务在shutdown_service()
期间记录消息。
实施自定义服务时,需要考虑以下几点:
对于最后两点,dir_monitor
I / O对象展示了用户可能不期望的行为。由于服务中的单个线程在单个实现的事件队列上调用阻塞操作,因此它有效地阻止了可能为其各自的实现立即完成的操作:
boost::asio::io_service io_service;
boost::asio::dir_monitor dir_monitor1(io_service);
dir_monitor1.add_directory(dir_name1);
dir_monitor1.async_monitor(&handler_A);
boost::asio::dir_monitor dir_monitor2(io_service);
dir_monitor2.add_directory(dir_name2);
dir_monitor2.async_monitor(&handler_B);
// ... Add file to dir_name2.
{
// Use scope to enforce lifetime.
boost::asio::dir_monitor dir_monitor3(io_service);
dir_monitor3.add_directory(dir_name3);
dir_monitor3.async_monitor(&handler_C);
}
io_service.run();
虽然与handler_B()
(成功)和handler_C()
(已中止)相关联的操作不会阻止,basic_dir_monitor_service
中的单个线程会被阻止,等待更改为dir_name1