我正在使用 epoll(边缘触发)和非阻塞套接字创建一个多线程服务器。目前,我正在主线程上创建一个事件循环并等待通知,它可以正常工作
我必须在两种方法之间进行选择以使其成为多线程:
如果我使用第一种方法,多个线程是否有机会收到同一事件的通知?我该如何处理这种情况?
最好的方法是什么? 谢谢你。
我认为选项 1 更受欢迎,因为非阻塞 IO 的主要目的是避免创建和销毁线程的开销。
以流行的Web服务器nginx为例,它创建多个进程(不是线程)来处理一个句柄上传入的事件,并在子进程中处理事件。它们都共享同一个监听套接字。它与选项 1 非常相似。
我也在使用
epoll
编写一个服务器,并且我已经考虑了与您所附的相同的模型。
使用方案1是可以的,但是可能会造成“惊群效应”,可以阅读nginx源码找到解决方案。至于选项2,我认为最好使用线程池,而不是每次生成一个新线程。
您还可以选择以下型号:
主线程/进程:
accept
传入阻塞IO连接,并使用BlockingList将fd发送到其他线程或使用PIPE
发送到其他进程。
子线程/进程:分别创建
epoll
的实例,并将传入的fd添加到epoll
中,然后用非阻塞IO处理。
每个线程的事件循环是最灵活且高性能的 你应该为每个事件循环创建一个epoll fd,不用担心epoll线程安全问题。
epoll是线程安全的,一个好的解决方案是你的主进程停留在accept(2)中,一旦你获得文件描述符,在目标线程的epoll fd中注册该文件描述符,这意味着每个线程都有一个epoll队列,一旦创建了线程,您就在调用 pthread_create(3) 中共享 epoll 文件描述符作为参数,因此当新连接到达时,您可以使用目标线程的 epoll fd 执行 epoll_ctl(...EPOLL_CTL_ADD..)在accept(2)之后创建的新套接字有意义吗?
每个答案都说 epoll 是线程安全的,但事实并非如此。
在最基本的层面上它确实是线程安全的:在考虑事件时,使用
EPOLLET
将只允许一个线程接收事件,而不使用 EPOLLET
(级别)可能会向多个线程传递相同的事件。
但是,它在调度级别上不是线程安全的:当线程接收到事件时(例如,它会因来自网络的第一个数据包而被唤醒),那么下一个数据包将触发第二个线程将触发的另一个事件捡起。现在您已经有两个线程同时运行以从网络获取数据。人们可以想象几种可能会中断的场景,具体取决于读取模式。除了这种竞争之外,操作系统调度可能会扰乱时间并有利于两个线程之一,当线程需要多次读取时,这会出现问题(例如,它先读取大小,然后读取有效负载)