Linux epoll 与多路复用

原文地址(略有修改):阻塞、非阻塞、异步、同步以及select/poll和epoll
针对IO,总是涉及到阻塞、非阻塞、异步、同步以及select/poll和epoll的一些描述,那么这些东西到底是什么,有什么差异?

一般来讲一个IO分为两个阶段:

  1. 等待数据到达
  2. 把数据从内核空间拷贝到用户空间

现在假设一个进程/线程A,发出IO请求,有两种情况:

  1. 立即返回
  2. 由于数据未准备好,需要等待,让出CPU给别的线程,自己sleep

第一种情况就是非阻塞,A为了知道数据是否准备好,需要不停的询问,而在轮询的空歇期,理论上是可以干点别的活,例如喝喝茶、泡个妞。第二种情况就是阻塞,A除了等待就不能做任何事情。

数据终于准备好了,A现在要把数据取回去,有几种做法:

  1. A自己把数据从内核空间拷贝到用户空间。
  2. A创建一个新线程(或者直接使用内核线程),这个新线程把数据从内核空间拷贝到用户空间。

第一种情况,所有的事情都是同一个线程做,叫做同步,有同步阻塞(BIO)、同步非阻塞(NIO)。第二种情况,叫做异步,只有异步非阻塞(AIO)

同步阻塞

同一个线程在IO时一直阻塞,直到读取数据成功,把数据从核心空间拷贝到用户空间

同步非阻塞

同一个线程发起IO后,立即获得返回,后面定期轮询数据读取情况,发现数据读取成功,把数据从核心空间拷贝到用户空间

异步非阻塞

一个线程发起IO后,立即返回,由另外的线程发现数据读取成功,把数据从核心空间拷贝到用户空间。

多路复用

select是几乎所有unix、linux都支持的一种多路IO方式,通过select函数发出IO请求后,线程阻塞,一直到数据准备完毕,然后才能把数据从核心空间拷贝到用户空间,所以select从内核上来说是同步阻塞方式。

poll对select的使用方法进行了一些改进,突破了最大文件数的限制,同时使用更加方便一些。

通过poll函数发出IO请求后,线程阻塞,直到数据准备完毕,poll函数在pollfd中通过revents字段返回事件,然后线程把数据从核心空间拷贝到用户空间,所以poll同样是同步阻塞方式,性能同select相比没有改进。

epoll是linux为了解决select/poll的性能问题而新搞出来的机制,基本的思路是:由专门的内核线程来不停地扫描fd列表,有结果后,把结果放到fd相关的链表中,用户线程只需要定期从该fd对应的链表中读取事件就可以了。同时,为了节省把数据从核心空间拷贝到用户空间的消耗,采用了mmap的方式,允许程序在用户空间直接访问数据所在的内核空间,不需要把数据copy一份。

epoll主要工作流程如下:

  1. 创建epoll文件描述符
  2. 把需要监听的文件fd和事件加入到epoll文件描述符,也可以对已有的fd进行修改和删除。文件fd保存在一个红黑树中,该fd的事件保存在一个链表中(每个fd一个事件链表),事件由内核线程负责填充,用户线程读取
  3. epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程
  4. 文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用
  5. ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
  6. ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
  7. ep_send_events函数扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间。事件发生后,读取事件对应的epoll_data,该结构中包含了文件fd和数据地址,由于采用了mmap,程序可以直接读取数据。

有人把epoll这种方式叫做同步非阻塞(NIO),因为用户线程需要不停地轮询,自己读取数据,看上去好像只有一个线程在做事情。

也有人把这种方式叫做异步非阻塞(AIO),因为毕竟是内核线程负责扫描fd列表,并填充事件链表的。

个人认为真正理想的异步非阻塞,应该是内核线程填充事件链表后,主动通知用户线程,或者调用应用程序事先注册的回调函数来处理数据,如果还需要用户线程不停的轮询来获取事件信息,就不是太完美了,所以也有不少人认为epoll是伪AIO。

LT模式与ET模式

以前select/poll中,每次遍历fd列表,发现fd可写、可读或异常后,就把bit置1(select)或返回对应事件(poll)。

epoll同样支持这种方式,每次fd可写、可读或异常后,就写入事件到事件链表中。此外,epoll还支持只在事件发生变化时才写入事件链表,例如如果事件一直是可读,则只在第一次写入链表。

这两种方式分别叫做水平触发(Level Triggered)和边沿触发(Edge Triggered),简称LT和ET。LT是缺省的工作方式,同时支持block和no-block socket,ET只支持no-block socket。异步事件驱动框架Netty底层采用的就是epoll + ET模式,而 JDK NIO中采用的是epoll + LT模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值