Sylar源码

374 阅读4分钟

本文主要涉及:

  • 线程模块
  • 协程模块
  • 协程调度模块
  • IO协程调度模块
  • 定时器模块
  • hook模块

同时这些模块也是Sylar的核心所在。

概念辨析

IO模型(阻塞与非阻塞)

阻塞与非阻塞是对于文件描述符fd而言的,即用来形容fd的。

  • socket在创建时默认是阻塞的
  • 可以在socket()这个系统调用的第二个参数中传SOCK_NONBLOCK标志
  • 或者使用fcntl系统调用的F_SETFL命令修改为非阻塞。

针对阻塞fd执行的系统调用可能因为无法立即完成而阻塞,直到要做的事件做完(connectacceptsendrecv

image.png

  • 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通知手段
    • selectpollepoll_wait
    • IO复用函数本身就是阻塞的,他们能提高效率的原因是他们能同时监听多个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复用和selectpollepoll混在一起。

私以为,无论selectpoll还是epoll,作用都是监听多个文件描述符上发生的可读、可写等事件,至于IO复用是他们三个能实现的一个目的罢了(差不多是开车和旅行的关系?)。

注意呀,他们三的功能是监听事件!!!!

这里我只谈epoll

事件驱动与异步的关系

目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:

  1. 有一个事件(消息)队列;
  2. 鼠标按下时,往这个队列中增加一个点击事件(消息);
  3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
  4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;

事件驱动是实现异步执行的方式之一。但并非所有异步系统都使用事件。

协程模块

image.png