本文主要涉及:
- 线程模块
- 协程模块
- 协程调度模块
- IO协程调度模块
- 定时器模块
- hook模块
同时这些模块也是Sylar的核心所在。
概念辨析
IO模型(阻塞与非阻塞)
阻塞与非阻塞是对于文件描述符fd而言的,即用来形容fd的。
- socket在创建时默认是阻塞的
- 可以在
socket()这个系统调用的第二个参数中传SOCK_NONBLOCK标志 - 或者使用
fcntl系统调用的F_SETFL命令修改为非阻塞。
针对阻塞的fd执行的系统调用可能因为无法立即完成而阻塞,直到要做的事件做完(connect、accept、send、recv)
connect:三次握手阶段accept:还没有链接连入send:还没将用户层的buffer中数据拷贝到内核层TCP的buffer中- 使用TCP协议进行通信的双方,各自都会有一个发送缓冲区(send buffer)和接收缓冲区(receive buffer)
- 发生阻塞的本质原因就是因为缓冲区满了
recv:内核层TCP的buffer中没有数据可以拷贝到用户层的buffer中来- read函数发生阻塞,是因为TCP的receive buffer中没有数据。也就是说发送端的数据还没有发送过来。
针对非阻塞的fd执行的系统调用总是立即返回,如果事件还没做完或发生,则返回-1,和出错一样,我们需要根据errno来区别。
很显然,我们需要在事件已经发生或准备好的情况下再使用非阻塞IO,这样才能提高程序效率。
因此,非阻塞IO需要和IO通知机制一起使用。
IO通知机制
何为IO事件通知机制,就是我们可以往这个机制中注册IO事件,一旦这些事件就绪,它就会通知上层就绪。
注意,仅仅是通知而已,对IO的读和写等操作依旧需要read、write等操作。
其中,IO事件通知机制包括:
-
I/O复用
- 最常见的IO通知手段
select、poll和epoll_waitIO复用函数本身就是阻塞的,他们能提高效率的原因是他们能同时监听多个IO事件
-
SIGIO信号
- 可以为一个目标文件描述符指定宿主进程
- 被指定的宿主进程就捕获到SIGIO信号
- 当目标文件描述符上有事件发生时,SIGIO信号的信号处理函数被触发
异步IO
理论上,阻塞IO,IO复用(非阻塞IO+IO复用的通知机制),信号驱动IO(非阻塞IO+SIGIO信号)都是同步IO模型。该模型的IO读写操作都是在IO事件就绪后,由应用程序来完成的。
异步IO由内核执行读写操作并触发读写完成事件,程序没有阻塞阶段。
Linux环境下,aio.h头文件提供了对异步IO的支持。
事件处理模式
服务器程序通常需要处理三类事件:
- I/O事件
- 信号事件
- 定时事件
有两种事件处理模式:
- Reactor(主线程将准备就绪的fd交给工作线程处理)
- 常与同步IO模型配合
- 主线程(IO处理单元)
- 只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知给工作线程(逻辑单元)。除此之外,主线程不做任何其他实质性的工作
- 工作线程(逻辑单元)
- 读写数据,接受新的连接,以及处理客户请求
- Proactor
- 常与异步IO模型配合
IO复用 (事件监听)
我一直觉得这个名字特别有误导性,总把IO复用和select,poll,epoll混在一起。
私以为,无论select,poll还是epoll,作用都是监听多个文件描述符上发生的可读、可写等事件,至于IO复用是他们三个能实现的一个目的罢了(差不多是开车和旅行的关系?)。
注意呀,他们三的功能是监听事件!!!!
这里我只谈epoll
事件驱动与异步的关系
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
- 有一个事件(消息)队列;
- 鼠标按下时,往这个队列中增加一个点击事件(消息);
- 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
- 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
事件驱动是实现异步执行的方式之一。但并非所有异步系统都使用事件。