前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >5.epoll的水平触发和边缘触发

5.epoll的水平触发和边缘触发

作者头像
灰子学技术
发布2020-10-30 14:43:25
4.6K0
发布2020-10-30 14:43:25
举报
文章被收录于专栏:灰子学技术

本篇是多路复用的第五篇,主要来讲解epoll的水平触发和边缘触发是怎么回事。

一、概念介绍

EPOLL事件有两种模型,水平出发和边缘触发,如下所示:

1. Level Triggered (LT) 水平触发

代码语言:javascript
复制
1. socket接收缓冲区不为空 有数据可读 读事件一直触发
2. socket发送缓冲区不满 可以继续写入数据 写事件一直触发
备注:符合思维习惯,epoll_wait返回的事件就是socket的状态

例子介绍:

代码语言:javascript
复制
1. accept一个连接,添加到epoll中监听EPOLLIN事件
2. 当EPOLLIN事件到达时,read fd中的数据并处理
3. 当需要写出数据时,把数据write到fd中;
如果数据较大,无法一次性写出,那么在epoll中监听EPOLLOUT事件
4. 当EPOLLOUT事件到达时,继续把数据write到fd中;
如果数据写出完毕,那么在epoll中关闭EPOLLOUT事件

2. Edge Triggered (ET) 边沿触发

代码语言:javascript
复制
1. socket的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
2. socket的发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件
备注:仅在状态变化时触发事件

例子介绍:

代码语言:javascript
复制
1. accept一个一个连接,添加到epoll中监听EPOLLIN|EPOLLOUT事件
2. 当EPOLLIN事件到达时,read fd中的数据并处理,read需要一直读,直到返回EAGAIN为止
3. 当需要写出数据时,把数据write到fd中,直到数据全部写完,或者write返回EAGAIN
4. 当EPOLLOUT事件到达时,继续把数据write到fd中,直到数据全部写完,或者write返回EAGAIN

3.LT和ET两者比较:

代码语言:javascript
复制
1. 从ET的处理过程中可以看到,ET的要求是需要一直读写,直到返回EAGAIN,否则就会遗漏事件。
ET的编程可以做到更加简洁,某些场景下更加高效,但另一方面容易遗漏事件,容易产生bug。
2. LT的处理过程中,直到返回EAGAIN不是硬性要求,但通常的处理过程都会读写直到返回EAGAIN,
但LT比ET多了一个开关EPOLLOUT事件的步骤。
LT的编程与poll/select接近,符合一直以来的习惯,不易出错。

二 、内核调度实现方式

  • 在epoll_wait的时候,阻塞等待事件发生, 事件发生时通过回调挂到ready list链表中
  • epoll_wait返回, 处理ready list, 返回事件给调用者
  • 此时ET模式已经将事件从ready list中删除,LT模式中还存在
  • 此时假设应用程序处理完了事件, 再次epoll_wait. ET模式继续阻塞
  • LT模式由于ready list中依然存在事件则不会阻塞, 对这些socket调用poll方法获取最新的事件信息,如果确认没事件了才会删除。

三、 水平触发和边缘触发的常见问题

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

  1. 内核:收到一个新建连接的请求
  2. 内核:由于 “惊群效应” ,唤醒两个正在 epoll_wait() 的线程 A 和线程 B
  3. 线程A:epoll_wait() 返回
  4. 线程B:epoll_wait() 返回
  5. 线程A:执行 accept() 并且成功
  6. 线程B:执行 accept() 失败,accept() 返回 EAGAIN

2. 边缘触发的问题:不必要的唤醒以及饥饿

1)不必要的唤醒:

代码语言:javascript
复制
1.内核:收到第一个连接请求。线程 A 和 线程 B 两个线程都在 epoll_wait() 上等待。
由于采用边缘触发模式,所以只有一个线程会收到通知。这里假定线程 A 收到通知
2.线程A:epoll_wait() 返回
3.线程A:调用 accpet() 并且成功
4.内核:此时 accept queue 为空,所以将边缘触发的 socket 的状态从可读置成不可读
5.内核:收到第二个建连请求
6.内核:此时,由于线程 A 还在执行 accept() 处理,只剩下线程 B 在等待 epoll_wait(),
于是唤醒线程 B。
7.线程A:继续执行 accept() 直到返回 EAGAIN
8.线程B:执行 accept(),并返回 EAGAIN,此时线程 B 可能有点困惑(“明明通知我有事件,结果却返回 EAGAIN”)
9.线程A:再次执行 accept(),这次终于返回 EAGAIN

2)饥饿:

代码语言:javascript
复制
1.内核:接收到两个建连请求。线程 A 和 线程 B 两个线程都在等在 epoll_wait()。
由于采用边缘触发模式,只有一个线程会被唤醒,我们这里假定线程 A 先被唤醒
2.线程A:epoll_wait() 返回
3.线程A:调用 accpet() 并且成功
4.内核:收到第三个建连请求。由于线程 A 还没有处理完(没有返回 EAGAIN),
当前 socket 还处于可读的状态,由于是边缘触发模式,所有不会产生新的事件
5.线程A:继续执行 accept() 希望返回 EAGAIN 再进入 epoll_wait() 等待,
然而它又 accept() 成功并处理了一个新连接
6.内核:又收到了第四个建连请求
7.线程A:又继续执行 accept(),结果又返回成功

参考文档:

https://blog.csdn.net/dongfuye/article/details/50880251

https://www.zhihu.com/question/20502870

https://blog.lucode.net/linux/epoll-tutorial.html

https://plantegg.github.io/2019/12/09/epoll%E7%9A%84LT%E5%92%8CET/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 灰子学技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 边缘触发的问题:不必要的唤醒以及饥饿
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档