为什么 Boost.Asio 不支持基于事件的接口?

2021-12-24 00:00:00 event-handling c++ boost boost-asio

我正在尝试理解 Boost.Asio,目的是潜在地使用条件变量和 Boost.Asio 来实现一个信号系统.

I am attempting to understand Boost.Asio, with the intention of potentially implementing a signaling system using condition variables in conjunction with Boost.Asio.

我看过其他 StackOverflow 问题boost asio asynchronously waiting on a条件变量、boost::asio异步条件和提升条件变量问题,但这些问题/答案都没有令人满意地触及我的一个基本问题:是Boost.Asio 不适用于或自然适合条件变量,并且/或者是否存在根本原因?

I have seen the other StackOverflow questions boost asio asynchronously waiting on a condition variable, boost::asio async condition, and boost condition variable issue, but none of these questions/answers have satisfactorily touched on an essential question that I have: Is it true that, and/or is there a fundamental reason why, Boost.Asio is not applicable to, or a natural fit with, condition variables?

我的想法是条件变量是使用操作系统级同步对象在内部实现的(例如,Windows 上的 boost::thread::condition_variable 使用的是 Windows 操作系统信号量).因为,以我目前的理解,boost::asio::io_service 旨在封装 OS 级同步对象,因此条件变量似乎很合适.

My thinking is that condition variables are internally implemented using operating-system level synchronization objects (for example, boost::thread::condition_variable on Windows uses a Windows OS semaphore). Because, to my current understanding, boost::asio::io_service is intended to encapsulate OS-level synchronization objects, condition variables would therefore seem to be a natural fit.

确实,与文件操作和套接字操作不同,操作系统级别通常从不存在与信号条件相关联的回调函数(我认为 - 我我不确定这一点).然而,在 Boost.Asio 中实现这样的回调处理程序似乎很简单,只需要求用户提供一个回调函数,当条件变量发出信号时调用该回调函数――就像用户必须为其他函数提供完成处理程序例程一样boost::asio::io_service 服务.

It is true that unlike file operations and socket operations, there is typically never a callback function at the operating system level associated with a signaled condition (I think - I am not sure about this). However, it would seem simple enough to implement such a callback handler within Boost.Asio by simply requiring the user to provide a callback function that is to be called when a condition variable is signaled - just as users must provide a completion handler routine for other boost::asio::io_service services.

例如(这只是一个快速的想法,不是一个完整的原型――它没有包含足够的参数来处理 notify_one() 与 notify_all(),并没有表明服务如何知道何时退出,并且可能有其他明显的遗漏或缺陷):

For example (this is just a quick thought, not a complete prototype - it does not include sufficient parameters to deal with notify_one() vs. notify_all(), doesn't indicate how the service knows when to exit, and likely has other glaring omissions or flaws):

void condition_handler_function() {}
boost::asio::io_service service;
boost::mutex mut;
boost::condition_variable cond;

// The following class is **made up by me** - would such a class be a good idea?
boost::asio::io_service::condition_service
             condserv(service, cond, mut, condition_handler_function); 

condserv.async_wait_on_signal();

service.run(); // when condition variable is signaled by notify_one(),
               // 'handler_function()' would be called


// ... in some other thread, later:
cond.notify_one(); // This would trigger 'handler_function()'
                   // in this theoretical code

也许,如果我试图填写代码片段上面提到的缺失细节,我会很清楚这无法以干净的方式工作.然而,这项工作并非易事.

Perhaps, if I tried to fill in the missing details noted above the code snippet, it would become clear to me that this could not work in a clean way. However, this effort is non-trivial.

因此,我想在这里发布问题.Boost.Asio 不支持条件变量是否有充分的理由?

Therefore, I would like to post the question here. Is there a good reason why condition variables are not supported by Boost.Asio?

附录

我已将帖子标题更改为引用基于事件的界面",因为下面的 Tanner 的回答已向我阐明,它确实是我所询问的基于事件的界面(不是真正的条件变量).

I have changed the title of the post to reference "Event-based interface", since Tanner's answer, below, has clarified to me that it is really an Event-based interface that I am asking about (not really condition variables).

推荐答案

Boost.Asio 是一个用于网络和低级 I/O 编程的 C++ 库.因此,操作系统级别的同步对象(例如条件变量)不在库的范围内,并且更适合 Boost.Thread.Boost.Asio 作者经常将 boost::asio::io_service 作为应用程序和操作系统之间的桥梁或链接.虽然这可能过于简化,但它是在操作系统的 I/O 服务范围内.

Boost.Asio is a C++ library for network and low-level I/O programming. As such, OS-level synchronization objects, such as condition variables, are outside of the scope of the library, and a much better fit for Boost.Thread. The Boost.Asio author often presents the boost::asio::io_service as the bridge or link between the application and the OS. While this may be an over simplification, it is within the context of the OS's I/O services.

由于操作启动和完成之间的时间和空间分离,异步编程已经具有固有的复杂性.Strands 提供了一个相当干净的解决方案来提供严格的顺序调用处理程序,无需显式锁定.由于锁定是隐式和线程安全的,应用程序代码可以使用链而不必担心死锁.另一方面,让 boost::asio::io_service::condition_service 对外部提供的对象执行隐式同步可能会将复杂的库变成复杂的库.应用程序开发人员可能不清楚处理程序在哪个互斥体上同步,以及互斥体的状态.此外,它还为应用程序引入了由于隐式锁定而更容易死锁事件处理循环的能力.

Asynchronous programming already has an innate complexity due to the separation in time and space between operation initiation and completion. Strands provided a fairly clean solution to provide strict sequential invocation of handlers, without the need of explicit locking. As the locking is both implicit and thread-safe, application code can use strands without the fear of deadlocking. On the other hand, having boost::asio::io_service::condition_service perform implicit synchronization on an externally provided object may turn a complex library into a complicated one. It may not be clear to the application developer what mutex on which the handler was synchronized, and the state of the mutex. Additionally, it introduces the ability for applications to more easily deadlock the event processing loop due to the implicit locking.

如果需要发生基于事件的处理程序调用,那么一种相当简单的替代方法是使用与 Boost.Asio 的 超时服务器 示例使用:boost::asio::deadline_timer.deadline_timer 的到期时间可以设置为 posix_time::pos_infin,从而导致 async_wait 的处理程序仅在计时器取消后才被调用:

If event-based handler invocation needs to occur, then one fairly simple alternative is use the same approach Boost.Asio's timeout server example uses: boost::asio::deadline_timer. A deadline_timer's expiry time can be set to posix_time::pos_infin, causing an async_wait's handler to only be invoked once the timer has been canceled:

  • cancel() 可以用作 notify_all(),其中所有未完成的处理程序都排队等待调用.
  • cancel_one() 可以用作 notify_one(),其中最多有一个未完成的处理程序排队等待调用.
  • cancel() could function as notify_all(), where all outstanding handlers are queued for invocation.
  • cancel_one() could function as notify_one(), where a max of one outstanding handler is queued for invocation.

一个简单的例子,忽略错误代码处理,如下:

A simple example, ignoring error code handling, is as follows:

#include <iostream>

#include <boost/asio.hpp>
#include <boost/thread.hpp>

class event
{
public:
  explicit
  event(boost::asio::io_service& io_service) 
    : timer_(io_service)
  {
    // Setting expiration to infinity will cause handlers to
    // wait on the timer until cancelled.
    timer_.expires_at(boost::posix_time::pos_infin);
  }

  template <typename WaitHandler>
  void async_wait(WaitHandler handler)
  {
    // bind is used to adapt the user provided handler to the deadline 
    // timer's wait handler type requirement.
    timer_.async_wait(boost::bind(handler));
  }

  void notify_one() { timer_.cancel_one(); }
  void notify_all() { timer_.cancel();     }

private:
  boost::asio::deadline_timer timer_;
};

void on_event() { std::cout << "on_event" << std::endl; }

int main()
{
  boost::asio::io_service io_service;
  event event(io_service);

  // Add work to service.
  event.async_wait(&on_event);

  // Run io_service.
  boost::thread thread(boost::bind(&boost::asio::io_service::run,
                       &io_service));

  // Trigger event, causing the on_event handler to run.
  event.notify_one();

  thread.join();  
}

相关文章