3. 传统IO数据拷贝、同步阻塞IO在哪里阻塞、同步阻塞IO怎么解决、IO多路复用、多路复用的特点、常见多路复用方案
阅读顺序
1. CPU查找物理寻址和虚拟寻址、虚拟内存优点、不同位数的操作系统虚拟内存大小关系、Linux虚拟内存用户空间和内核空间
2. 进程切换、上下文及上下文切换、进程阻塞、文件描述符
3. 传统IO数据拷贝、同步阻塞IO在哪里阻塞、同步阻塞IO怎么解决、IO多路复用、多路复用的特点、常见多路复用方案
传统IO数据拷贝
比如一个Redis的读取操作,Redis的进程是在用户空间的,用户空间会通过系统调用去读取FD(文件描述符),通过系统调用比如通过 read/write去操作文件描述符 读取/写入数据,如果说这块文件的数据已经在用户空间缓存了,那就可以直接读到,如果数据是不存在的,那必须要先从磁盘硬件设备加载数据到内核的缓冲区里面,再从内核缓冲区复制数据到用户的缓冲区,那么在这个过程中,就发生了多次的数据拷贝,和上下文的切换
同步阻塞IO在哪里阻塞
当通过系统调用比如通过 read/write去操作文件描述符 读取/写入数据,但这个文件描述符当前是不可读的,那么系统不会直接给响应,也就是说,阻塞的地方是两个过程:
- ①从磁盘硬件设备加载数据到内核的缓冲区里面这个过程是阻塞的
- ②从内核缓冲区复制数据到用户的缓冲区这个过程也是阻塞的
直到从内核空间复制数据完毕,才会返回结果给到用户空间,然后阻塞的状态才会解除
同步阻塞IO怎么解决呢?
思路1:多线程处理阻塞的过程
在服务端(内核空间)使用线程池去创建多个线程来处理阻塞的两个过程
①从磁盘硬件设备加载数据到内核的缓冲区里面
②从内核缓冲区复制数据到用户的缓冲区
可以使用多线程,但不是一个很好的方案。
如果并发比较大,就会需要创建很多线程,导致线程数很大,可能造成系统不能够承受,而且线程数太多,创建线程和释放线程会比较消耗资源。
思路2:阻塞会造成用户空间傻等,让他不再傻等
用户空间不再傻等,而是轮询问内核空间有没有准备好,直到准备好之后返回数据,轮询的这种方式就叫做非阻塞IO了,因为不需要客户端(用户空间)一直等待。
这种方案,实时性不高,取决于轮询间隔,间隔大了,读取到的时间差就会很大,间隔太小,对于客户端(用户空间)也比较消耗性能。
所以服务端有没有更好的解决方案: 服务端就用一个线程,去处理多个客户端的请求呢?可以,IO多路复用 multiplexing。
IO多路复用 multiplexing
I/O: 指网络I/O
多路: 多个TCP连接(Socket或Channel,可以理解为多个客户端的连接)
复用: 复用服务端的一个或多个线程
设计思想是: 在服务端由内核去帮助应用程序去监视 FD(文件描述符) 是否就绪(数据有没有准备好),不再由客户端监视连接,由服务端去监视连接。
它是怎么做的呢?
在客户端操作的时候会有一个Socket,这个Socket是由事件类型的,服务端会有一个程序,IO多路复用器(或者叫IO多路复用程序)会将这些Socket的请求放入一个队列中,然后通过文件时间分派器转发到不同的事件处理器(如命令请求处理器、命令回复处理器等)。
多路复用是需要操作系统支持的,不同的操作系统有不同的实现。
多路复用的特点
通过一种机制,一个进程能同时等待多个 FD(文件描述符) ,而这些 FD(文件描述符) 其中的任意一个进入读就绪(readable)状态,select()函数就可以返回,不需要你去做轮询。
常见多路复用方案
- evport是Solaris系统内核提供支持的
- epoll是Linux系统内核提供支持的
- kqueue是Mac系统内核提供支持的
- select是POSIX提供的,一般的操作系统都有支撑(是一个保底方案,性能不如其它方案,基本上不会再使用)。
不同的多路复用的方案有各自的特点,epoll用的是最多的(以Redis的多路复用方案为例,源码ae.c定义epoll优先级最高,kqueue次之,保底是select)。
Redis多路复用
Redis源码:Redis源码怎么查看入门、Redis外部数据结构到Redis内部数据结构查看源码顺序、Redis源码查看顺序
Redis多路复用方案定义在ae.c文件
ae.c源码文件在解压后的src目录下
/* Include the best multiplexing layer supported by this system. 包含该系统支持的最佳多路复用层
* The following should be ordered by performances, descending. 以下应按顺序依次递减*/
/*epoll优先级最高,kqueue次之,保底是select*/
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
#endif