即使在文件描述符不可用的情况下,也能有效地侦听多个套接字

Efficiently listening on multiple sockets even when file descriptors are not available

本文关键字:有效地 套接字 情况下 文件 描述      更新时间:2023-10-16

假设我有两个库(A和B),每个库都有一个监听套接字的函数。这些函数使用select(),如果数据到达,它们会立即返回一些事件,否则它们会等待一段时间(超时),然后返回NULL:

A_event_t* A_wait_for_event(int timeout);
B_event_t* B_wait_for_event(int timeout); 

现在,我在我的程序中使用它们:

int main (int argc, char *argv[]) {
// Init A
// Init B
// .. do some other initialization
    A_event_t *evA;
    B_event_t *evB;
    for(;;) {
        evA = A_wait_for_event(50);
        evB = B_wait_for_event(50);
        // do some work based on events
    }
}

每个库都有自己的套接字(例如udp套接字),并且不能从外部访问。

问题:这不是很有效。例如,如果有很多事件等待*B_wait_for_event*传递,那么这些事件将不得不一直等到*a_wait_for-event*超时,这实际上限制了库B和我的程序的吞吐量。

通常,可以使用线程来分离处理,但如果某个事件的处理需要调用其他库的函数,反之亦然。示例:

if (evA != 0 && evA == A_EVENT_1) {
    B_do_something();
}
if (evB != 0 && evB == B_EVENT_C) {
    A_do_something();
}

因此,即使我可以创建两个线程并将功能从库中分离出来,这些线程也必须在它们之间交换事件(可能通过管道)。这仍然会限制性能,因为一个线程会被*X_wait_for_event()*函数阻塞,并且不可能立即从其他线程接收数据。

如何解决这个问题?

此解决方案可能不可用,具体取决于您使用的库,但最好的解决方案是而不是调用单个库中等待事件的函数。每个库都应该支持挂接到外部事件循环中。然后,您的应用程序使用一个包含poll()select()调用的循环,该调用将等待您使用的所有库要等待的所有事件。

glib的事件循环对此很好,因为许多库已经知道如何连接到它。但如果你不使用像glib这样复杂的东西,正常的方法是:

  • 永远循环:
    • 从一个无限计时器和一组空的文件描述符开始
    • 对于您使用的每个库:
      • 调用库中的设置函数,该函数可以将文件描述符添加到您的集合和/或缩短(但不能延长)超时时间
    • 运行poll()
    • 对于您使用的每个库:
      • 调用库中的调度函数,该函数将响应poll()返回时可能发生的任何事件

是的,早期的图书馆仍然有可能饿死后期的图书馆,但这在实践中是有效的。

如果你使用的库不支持这种设置&dispatch接口,将其作为一个特性添加,并向上游贡献代码!

如果您在一个线程中不允许调用A_do_something,而另一个线程正在执行A_wait_for_event(类似于B),那么我敢肯定,您不能做任何有效的事情,必须在各种邪恶之间解决。

最明显的改进是在收到事件后立即采取行动,而不是试图从两者中读取:即订购循环

  • 等待A事件
  • 也许在B做点什么
  • 等待B事件
  • 也许在A做点什么

你可以做的其他缓解措施是

  • 试着预测A事件还是B事件更有可能发生,然后先等待。(例如,如果他们连续出现,那么在获得并处理了一个A事件后,你应该回去等待另一个A活动)
  • 摆弄暂停值,在旋转循环和过多的阻挡之间取得平衡。(甚至可能动态调整)

编辑:您可以检查库的API;他们可能已经提供了一种解决问题的方法。例如,它们可能允许您注册事件的回调,并通过回调获取事件通知,而不是轮询wait_for_event

另一件事是,如果你可以为库创建新的文件描述符来侦听。例如,如果你创建了一个新的管道,并将一端交给库A,那么如果线程#1正在等待A事件,线程#2可以写入管道以使事件发生,从而迫使#1退出wait_for_event。有了随意将线程踢出wait_for_event函数的能力,各种各样的新选项都变得可用。

一个可能的解决方案是在"执行某些操作"的"main"线程中使用两个线程来处理wait_for_eventsboost::condition_variable。这里有一个相似但不精确的解决方案