多线程 epoll

13

我正在使用epoll(边缘触发)和非阻塞套接字创建一个多线程服务器。目前,我在主线程上创建了一个事件循环并等待通知,它可以正常工作。
我必须在以下两种方法之间进行选择,以使其支持多线程:

  1. 为每个线程创建一个事件循环,并将服务器套接字的文件描述符添加到每个线程上查找通知。(这是否可行?我的意思是:epoll是否支持线程安全?)
  2. 创建单个事件循环并等待通知。每当收到通知时,生成一个线程来处理。

如果我使用第一种方法,有可能多个线程会收到相同的事件通知吗?我应该如何处理这种情况?

哪种方法最好呢? 谢谢。


2
可能是Is epoll thread-safe?的重复问题。 - user405725
3
是的,epoll 是线程安全的。是的,你可以这么做。只需搜索相关详细信息,在 Stack Overflow 和 Google 上都能找到这些信息。 - user405725
谢谢。我还想知道是否可以将同一文件描述符添加到多个 epoll 循环中。可以吗? - goodolddays
6个回答

7

我认为选项1更受欢迎,因为非阻塞IO的主要目的是避免创建和销毁线程的开销。

以流行的Web服务器nginx为例,它创建多个进程(而不是线程)来处理句柄上的传入事件,并在子进程中处理这些事件。它们都共享同一个监听套接字。这与选项1非常相似。


3
我也正在使用 epoll 编写服务器,我考虑了你附加的模型。可以使用选项1,但可能会引起"惊群效应",你可以阅读 nginx 的源代码找到解决方案。至于选项2,我认为最好使用线程池而不是每次生成一个新线程。
你还可以使用以下模型:
主线程/进程:使用阻塞IO接受传入连接,并使用 BlockingList 将 fd 发送给其他线程或使用 PIPE 发送给其他进程。
子线程/进程:分别创建 epoll 实例,并将传入的 fd 添加到 epoll 中,然后使用非阻塞IO进行处理。

1

每个线程都有一个事件循环是最灵活且性能高的。您应该为每个事件循环创建一个 epoll fd,不必担心 epoll 线程安全问题。


0
每个答案都说epoll是线程安全的,但事实并非如此。
在最基本的层面上,epoll调用是线程安全的:考虑文件描述符列表、事件等。特别是使用EPOLLET,只允许一个线程接收一个事件,而水平触发的epoll可能会将同一个事件传递给多个线程,这会在页面的其他地方引发所谓的"雷鸣群"问题。
然而,在调度级别上,它并不是线程安全的:当一个线程接收到一个事件(比如使用EPOLLET时,它会被唤醒来处理来自网络的第一个数据包),然后在处理事件列表中的其他文件描述符时,网络上的下一个数据包到达并触发另一个事件,另一个线程接收到该事件。现在,你有两个线程同时从网络中获取数据。
可以想象出几种情况,这可能会破坏应用程序,这取决于读取模式。除了这种竞争之外,操作系统的调度可能会干扰时间,并偏好其中一个线程,这在一个线程需要多次读取时会引发问题(例如,它首先读取大小,然后读取有效负载,但在两次读取之间被时间片切换)
即使第一个线程读取了所有内容,第二个线程也不会读取任何内容(除非在两次读取之间有数据从网络传输过来)。假设这个“所有内容”包含了一个HTTP请求的一半。当它解析这一半的请求时,第二半已经在网络上到达,并且另一个线程收到了一个事件并读取了第二半。如何在没有同步的情况下处理这个问题?
你描述的在多个线程中使用同一个套接字的方法只有在确保以下两点的情况下才是线程安全的:
1. 读取操作能够原子地检索到一定数量的完整消息,这几乎是不可能的,因为网络的工作方式(路径MTU,TCP窗口等)。在实践中,这只适用于固定长度的消息。
2. 消息可以无序处理。

0

0

epoll是线程安全的,一个好的解决方案是让你的主进程停留在accept(2)中,一旦你获得文件描述符,就将其注册到目标线程的epoll fd中,这意味着你为每个线程都有一个epoll队列,一旦创建线程,你可以将epoll文件描述符作为参数在调用pthread_create(3)时共享,因此当新连接到达时,你可以使用目标线程的epoll fd和accept(2)后创建的新套接字进行epoll_ctl(...EPOLL_CTL_ADD...),明白了吗?


为什么不在新客户端连接请求到达时将监听套接字描述符直接添加到epoll集合中,在主线程中accept连接,将新建的已连接套接字描述符添加到epoll集合中?当真正的数据到达时,再创建一个新线程来处理它。这样,程序看起来更加精确。 - zeekvfu

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