①BIO(blocking IO)
阻塞IO,即在读写数据的过程中会发生阻塞现象。
当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,操作系统就会将数据从内核空间拷贝到用户空间,并返回结果给用户线程,用户线程才解除阻塞状态。
(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)
典型的阻塞IO模型的例子为:
data = socket.read();
如果数据没有就绪,就会一直阻塞在read方法。
②NIO(nonblocking IO)
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时(数据未准备就绪),它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
典型的非阻塞IO模型一般如下:
while(true){ data = socket.read(); if(data!= error){ 处理数据 break; } }
但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。
③AIO (异步IO Asynchronous IO)
在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。
而另一方面,从内核的角度,当它收到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何阻塞。
然后,内核会等待数据准备完成,再将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。
当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
(也就说用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个请求。)
也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。
①IO多路复用(IO multiplexing)
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。
因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线 程和 进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
select:轮询所有的socket
poll:轮询所有的socket,但是和select相比,没有最大连接数的限制,原因是它是基于链表存储的
epoll:只会轮询发生了IO请求的socket。虽然连接数有上限,但是很大,1G内存的机器上可以打开10W左右的连接;
I/O 多路复用模型是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
总结:IO多路复用。“多路”指的是多个网络连接客户端,“复用”指的是复用同一个线程(单线程)。I/O多路复用是使用一个线程来检查多个socket的状态。在单个线程中通过记录跟踪每一个socket的状态来管理处理多个I/O流。
I/O多路复用模型是利用select、poll、epoll可以同时监察多个流的I/O事件的能力,在空闲的时候会把当前线程阻塞掉。当有一个或多个流有I/O事件时,就会从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll只轮询那些真正发出了事件的流)。如果有多个socket需要处理,就将这些socket对象放在队列中,文件事件分派器逐个读取队列中的socket并将其分发给不同的事件处理器进行处理。
poll本质上和select没有区别, 但是poll没有最大连接数的限制,原因是它是基于链表来存储的.
②信号驱动IO(signal driven IO)
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。
内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。
信号驱动 I/O 的 CPU 利用率很高。
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,
咱们下期见!答案获取方式:已赞 已评 已关~
学习更多知识与技巧,关注与私信博主(03)