C++ 线程之间的可轮询信令

C++ 线程之间的可轮询信令,c++,c,unix,C++,C,Unix,我正在从事一个项目,其中主服务器线程需要将事件分派给一系列工作线程。在工作线程中进行的工作依赖于轮询(即,epoll或kqueue,具体取决于所讨论的UNIX系统),需要处理这些操作的超时。这意味着,正常的条件变量或信号量结构不适用于此分派,因为它会造成一个或另一个块,从而在处理来自轮询的事件或来自服务器线程的事件之间产生不必要的延迟 因此,我想知道以可轮询方式在线程之间调度此类事件的最佳构造是什么?本质上,需要传递的只是一个可轮询的“信号”,它告诉工作线程它有更多的事件要获取。我已经研究过使用

我正在从事一个项目,其中主服务器线程需要将事件分派给一系列工作线程。在工作线程中进行的工作依赖于轮询(即,epoll或kqueue,具体取决于所讨论的UNIX系统),需要处理这些操作的超时。这意味着,正常的条件变量或信号量结构不适用于此分派,因为它会造成一个或另一个块,从而在处理来自轮询的事件或来自服务器线程的事件之间产生不必要的延迟

因此,我想知道以可轮询方式在线程之间调度此类事件的最佳构造是什么?本质上,需要传递的只是一个可轮询的“信号”,它告诉工作线程它有更多的事件要获取。我已经研究过使用UNIX管道(未命名管道,因为它是进程的内部管道),这似乎是一个不错的解决方案,因为一个字节可以写入管道,并在队列被清除时读取——但是,我想知道这是否是可用的最佳方法?还是最快的


或者,也可以在Linux上使用signalfd(2),但由于这在BSD系统上不可用,我宁愿避免这种构造。我还想知道使用系统信号的开销到底有多大?

就性能而言,系统调用的成本与其他操作相比是相当巨大的,因此重要的是系统调用的数量。有两种选择:

  • 按照你写的那样使用管道。如果您对消息有任何有用的负载,那么您将得到一个要发送的系统调用、一个要等待的系统调用和一个要接收的系统调用。尝试通过管道传递任何相关数据,而不是从共享结构读取数据,以避免锁定带来的额外开销
  • select
    poll
    具有变量,它们也会等待信号(
    pselect
    ppoll
    )。Linux
    epoll
    可以使用
    signalfd
    执行相同的操作,因此
    kqueue
    是否可以等待信号仍然是一个问题,我不知道。如果可以,您可以使用它们(无论如何,您在Linux和*BSD上使用的是不同的机制)。如果您没有很好地使用传递的数据,它将为您节省用于读取的系统调用

  • 我希望通过套接字传递数据会更有效,如果它允许您取消任何其他锁定。

    Jan Hudec的回答是正确的,尽管出于以下几个原因,我不建议使用信号:

    • glibc的旧版本以非原子方式模拟了
      pselect
      ppoll
      ,使它们基本上一文不值。即使正确使用掩码,在
      pthread\u sigprocmask
      select
      调用之间,信号也可能“丢失”,这意味着它们不会导致
      EINTR
    • 我不确定
      signalfd
      是否比管道更有效。(我还没有测试过它,但我没有任何特别的理由相信它是。)
    • 信号通常是一种很难纠正的问题。我在这些问题上花了很多精力(请参阅),如果可以的话,我建议您避免使用它们
    由于您正在尝试将异步处理移植到多个系统,因此我建议您查看libevent。它将为您提取
    epoll
    kqueue
    ,甚至在您添加新事件时代表您唤醒工作人员。看

    而且

    工作线程处理套接字I/O和异步磁盘I/O,这意味着它最好总是等待事件队列机制(epoll/kqueue)


    你在这里可能会感到失望。这些事件队列机制实际上不支持异步磁盘I/O。有关更多详细信息,请参阅。

    线程将从何处获取数据?如果我误解了这个问题,我很抱歉,但是通过轮询(2)调用简单地轮询线程将读取的任何输入源有什么错呢?我重新阅读了您的问题几次,但我仍然对您的问题有点模糊。。。听起来工作线程有一些死区时间(等待管道或其他东西),并且。。。那就是我迷路的地方。为什么死亡时间是相关的?你不想等他们完成他们的工作吗?显然我的解释被删除了,就这样。工作线程处理套接字I/O和异步磁盘I/O,这意味着它最好总是等待事件队列机制(epoll/kqueue)。问题是,新任务也会被传递给线程,但由于这些任务不一定依赖于I/O,我不能简单地将它们扔到特定工作线程的事件循环中。因此,我必须找到一种方法来通知这个事件轮询循环中的工作人员,新的应用程序事件可以处理。否则我就浪费时间了。正如你所想的,使用管道是一个不错的选择。您只需将其读取端添加到正在等待的文件描述符组中即可。感谢您在“信号与管道”决策中的输入。就轮询机制而言,我从一开始就实现了这些机制,以避免使用POSIX AIO库,所以这没什么大不了的,但首先要感谢您——尽管以我的经验来看,对于特定的事情来说,这会更好一些。谢谢!
    2058 static inline int
    2059 event_add_internal(struct event *ev, const struct timeval *tv,
    2060     int tv_is_absolute)
    2061 {
    ...
    2189         /* if we are not in the right thread, we need to wake up the loop */
    2190         if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
    2191                 evthread_notify_base(base);
    ...
    2196 }