盘点Linux Epoll那些致命弱点

本文分析了Linux epoll在多线程扩展性方面的问题,包括accept(2)和read(2)操作在不同场景下的挑战,如水平触发和边缘触发模式下的唤醒和数据乱序问题。同时,探讨了file descriptor与file description生命周期不一致带来的问题,当close(2)关闭fd时,可能导致epoll上报已关闭的fd事件。解决这些问题需要正确使用EPOLLONESHOT和EPOLLET等标志,或者避免直接使用close(2)。文章提醒开发者在使用epoll时需要注意这些潜在问题。
摘要由CSDN通过智能技术生成

1. 引言

本文来自 Marek’s 博客中 I/O multiplexing part 系列之三和四,原文一共有四篇,主要讲 Linux 上 IO 多路复用的一些问题,本文加入了我的一些个人理解,如有不对之处敬请指出。原文链接如下:

2. 脉络

系列三和系列四分别讲 epoll(2) 存在的两个不同的问题:

  1. 系列三主要讲 epoll 的多线程扩展性的问题
  2. 系列四主要讲 epoll 所注册的 fd (file descriptor) 和实际内核中控制的结构 file description 拥有不同的生命周期

我们在此也按照该顺序进行阐述。

3 epoll 多线程扩展性

epoll 的多线程扩展性的问题主要体现在做多核之间负载均衡上,有两个典型的场景:

  1. 一个 TCP 服务器,对同一个 listen fd 在多个 CPU 上调用 accept(2) 系统调用
  2. 大量 TCP 连接调用 read(2) 系统调用上

3.1 特定 TCP listen fd 的 accept(2) 的问题

一个典型的场景是一个需要处理大量短连接的 HTTP 1.0 服务器,由于需要 accept() 大量的 TCP 建连请求,所以希望把这些 accept() 分发到不同的 CPU 上来处理,以充分利用多 CPU 的能力。

这在实际生产环境是存在的, Tom Herbert 报告有应用需要处理每秒 4 万个建连请求;当有这么多请求的时候,很显然,将其分散到不同的 CPU 上是合理的。

然后实际上,事情并没有这么简单,直到 Linux 4.5 内核,都无法通过 epoll(2) 把这些请求水平扩展到其他 CPU 上。下面我们来看看 epoll 的两种模式 LT(level trigger, 水平触发) 和 ET(edge trigger, 边缘触发) 在处理这种情况下的问题。

3.1.1 水平触发的问题:不必要的唤醒

一个愚蠢的做法是是将同一个 epoll fd 放到不同的线程上来 epoll_wait(),这样做显然行不通,同样,将同一个用于 accept 的 fd 加到不同的线程中的 epoll fd 中也行不通。

这是因为 epoll 的水平触发模式和 select(2) 一样存在 “惊群效应”,在不加特殊标志的水平触发模式下,当一个新建连接请求过来时,所有的 worker 线程都都会被唤醒,下面是一个这种 case 的例子:

1. 内核:收到一个新建连接的请求
2. 内核:由于 "惊群效应&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值