I/O
流
同步异步
- 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
- 异步就是发一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时可以处理其他的请求,被调用者通常依靠事件、回调等机制来通知调用者其返回结果。
阻塞和非阻塞
- 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。不占用CPU资源。
- 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他的事情。占用CPU资源(轮询)
同步异步与阻塞非阻塞
1 | 老张烧开水的故事 |
阻塞IO - Blocking IO
- 一请求一应答
- 通常由一个独立的
Acceptor
线程负责监听客户端的连接。我们一般通过在while(true)
循环中服务端会调用accept()
方法等待客户端连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待当前连接的客户端的操作执行完成,不过可以通过多线程来支持多个客户端的连接
伪异步 I/O
当客户端并发访问量增加后阻塞IO模型会出随着并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。
线程是宝贵的资源。线程的创建和销毁成本很高,线程本身占用较大内存,线程的切换成本也很高,容易造成锯齿状的系统负载。
- 解决方案
后端通过一个线程池来处理多个客户端的请求接入,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
非阻塞IO - NoneBlocking IO
当用户线程发起一个 IO 操作后,并不需要等待,而是马上就得到一个结果。如果结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 IO 操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
在非阻塞IO 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
- 非阻塞式主要体现在用户进程发起recvfrom系统调用的时候,这个时候系统内核还没有接收到数据报,直接返回错误给用户进程,告诉“当前还没有数据报可达,晚点再来”
- 用户进程接收到信息,但是用户进程不知道什么时候数据报可达,于是就开始不断轮询(polling)向系统内核发起recvfrom的系统调用“询问数据来了没”,如果没有则继续返回错误
- 用户进程轮询发起recvfrom系统调用直至数据报可达,这个时候需要等待系统内核复制数据报到用户进程的缓冲区,复制完成之后将返回成功提示
IO多路复用 - IO multiplexing
所谓 I/O 多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要 select
、 poll
、 epoll
来配合。
在多路复用IO模型中,会有一个内核线程不断地去轮询多个 socket 的状态,只有当真正读写事件发送时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有真正有读写事件进行时,才会使用IO资源,所以它大大减少来资源占用。
- IO复用模式是使用select或者poll函数向系统内核发起调用,阻塞在这两个系统函数调用,而不是真正阻塞于实际的IO操作(recvfrom调用才是实际阻塞IO操作的系统调用)
- 阻塞于select函数的调用,等待数据报套接字变为可读状态
- 当select套接字返回可读状态的时候,就可以发起recvfrom调用把数据报复制到用户空间的缓冲区
信号驱动IO - signal driven IO
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接字几乎没用,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么请求。
异步IO - asynchronous IO
前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是从内核拷贝数据到用户态的过程都会让用户线程阻塞。
- 由POSIX规范定义,告知系统内核启动某个操作,并让内核在整个操作包含数据等待以及数据复制过程的完成之后通知用户进程数据已经准备完成,可以进行读取数据;
- 与上述的信号IO模型区分在于异步是通知我们何时IO操作完成,而信号IO是通知我们何时可以启动一个IO操作
IO模型对比
处理多I/O请求
阻塞+多进程/多线程
非阻塞+轮询
多路复用
- select 最多监听1024个 且不会通知具体哪个流接收到数据 需要遍历全部流,进行处理。 平台无关性。
1 | for { |
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
epoll 监听的数量与操作系统能打开的文件数相同,且返回收到数据的流。不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。Linux操作系统的方法。
1 | for { |