幼麟实验室 协程, 大丙 I/O多路复用


  • 线程是进程中的执行体,协程只有用户态栈
  • 协程实现了用户态多任务模型
  • 由于现在高并发场景频繁出现,之前的多进程模型的内存资源捉襟见肘
    多进程模型下,内核态与用户态多次转换,仍然疲于应对高并发场景
  • socket的所有操作都由os来完成,一个进程每创建一个socket,就会在打开文件描述符表中增加一个记录,返回给应用程序的只有socket描述符,每个TCP socket被创建时,os都会为它创建一个读缓冲区与一个写缓冲区要获得响应数据,就要从读缓冲区拷贝过来,要通过socket发送数据,也要把数据拷贝到写缓存区
  • 当执行读/写操作的时候,读/写缓冲区里没有数据,一种方式是线程让出CPU,等到数据就绪且线程获得时间片,就可以继续执行,这就是阻塞式I/O,就像mutex阻塞式I/O要处理一个socket,就要占用一个线程,一个线程处理完一个socket,才能处理下一个,在高并发场景下,会加剧调度开销
  • 非阻塞式I/O,有点像spinlock,也就是即使缓冲区中没有数据,也不让出CPU,而是频繁的去检查socket缓冲区是否就绪,这是一种忙等待容易造成空耗CPU,加剧响应延迟
  • I/O多路复用,OS把多个socket加入到监听集合,这样就可以通过一次系统调用(也就是陷入内核去检查socket缓冲区)监听多个socket,有socket就绪了,就可以逐个处理了,这样就不会为了等待某个socket而阻塞或者是忙等待。linux中有三种I/O多路复用的方式:
    • select可以设置要等待的描述符,也可以设置等待超时的时间,如果有准备好的fd或者达到超时时间select函数就会返回,select的函数签名中的参数表明,其可以监听读/写/异常三类事件,fd_set是一个unigned long类型的数组一共有16个元素,每个可以存储64位,也就是一共可以监听1024个文件描述符fd,这太少了,而且每次调用select都传递所有的监听集合,需要频繁从用户态到内核态的数据拷贝,初次之外,即便是存在fd就绪了,也需要遍历整个监听集合,这些都很影响性能
      • select跨平台,可以在W/M/L都可以使用,
    • poll,可以支持的fd数目变多了
    • epoll