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

12

我正在尝试理解Boost.Asio,以便使用条件变量与Boost.Asio结合实现信号系统。

我已经看到了其他StackOverflow问题:boost asio异步等待条件变量boost::asio异步条件boost条件变量问题,但这些问题/答案都没有令我满意地回答一个重要问题:是否真的如此,并且/或者是否有根本性原因,导致Boost.Asio不适用于条件变量或与其天然契合不上?

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

的确,与文件操作和套接字操作不同,通常情况下,操作系统级别的信号与已标记的条件通常没有关联的回调函数(我想 - 我不确定这一点)。但是,在Boost.Asio中实现这样一个回调处理程序似乎很简单,只需要求用户提供一个回调函数,在条件变量被标记时调用该函数 - 就像用户必须为其他boost::asio::io_service服务提供完成处理程序例程一样。

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

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

也许,如果我尝试填补上述代码片段中缺失的细节,清楚地了解到这不能以一种清晰的方式工作。然而,这需要非常努力的工作。

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

附:

我已将帖子标题更改为“基于事件的接口”,因为 Tanner 在下面的回答中澄清了我正在询问的确实是基于事件的接口(并非真正的条件变量)。

2个回答

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

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


如果需要进行基于事件的处理程序调用,则可以使用与Boost.Asio的timeout server示例相同的方法:boost::asio::deadline_timer。 可以将deadline_timer的到期时间设置为posix_time :: pos_infin,从而使async_wait的处理程序仅在计时器被取消后才被调用:
  • cancel() 可以作为 notify_all() 使用,其中所有未完成的处理程序都将排队等待调用。
  • cancel_one() 可以作为 notify_one() 使用,其中最多只有一个未完成的处理程序将排队等待调用。

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

#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();  
}

非常好的回答,谢谢。你说得对,我是在寻找Boost.Asio中的“事件”接口,而不是条件变量接口。如果有这样的事件接口可用,我会立即使用它。你认为将事件接口纳入Boost.Asio的预期范围内吗?如果是这样,我想知道是否建议作者或维护者这样做。 - Dan Nissenbaum
我已经更改了帖子的标题,包括“基于事件的接口”这个概念/短语。 - Dan Nissenbaum
@DanNissenbaum:嗯,这可能在范围内。我想提出建议/请求也不会有什么坏处。更清晰的界面肯定会使它更加明确,因为使用boost::asio::deadline_timer来获得所需的行为略微晦涩。 - Tanner Sansbury
非常好的答案。我能够无缝地使用这种技术改变我的条件变量逻辑。 - Tarc
今天,在反复阅读这个例子的过程中,突然间,"Bind 用于将用户提供的处理程序适应于截止定时器的等待处理程序类型要求"的巧妙之处在我脑海中一下子闪现。 - sehe
显示剩余2条评论

4

条件变量是一种同步概念:它们会阻塞一个线程,直到另一个线程发生了某些事情。Boost.Asio是一个异步框架:它以非阻塞的方式提供事件复用。这两者似乎不太兼容。如果您想要异步事件通知,请查看Linux上的eventfd,它应该可以与Boost.Asio一起使用。


当我读到这个问题时,我立刻想到使用具有信号量语义的eventfd - Sam Miller
3
另一方面,条件变量也可以被视为异步的,因为设置信号的线程不会阻塞,立即继续执行,并且不能假设工作线程何时会并行执行工作。此外,条件变量的同步方面可以通过Boost.Asio转换为异步系统。也就是说,当您将工作项放入boost::asio::io_service中时,它会立即由工作线程处理(如果空闲),方式与由信号触发的工作相同。这是我在这里建议的语义。 - Dan Nissenbaum
在Linux中,eventfd似乎类似于Windows操作系统级别的信号量。我怀疑在内部,boost :: asio :: io_service :: run(及其辅助程序)是基于阻塞OS原语实现的,例如Windows上的信号量,或Linux上的条件或eventfd。 - Dan Nissenbaum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接