首先来看一下同步与异步的概念:
1. 同步是指当前端发起一次操作请求时, 只有后台执行完所有的代码操作才会给前端返回值.
2. 异步是将前端发回的消息加入消息队列, 并且立刻给前端返回请求, 告诉用户可以离开当前页面去做别的事情. 当后台处理完成, 操作系统会通知事件和回调机制等通知相应的县城进行后续操作.
同步和异步最大的区别在于: 一个需要等待, 一个不需要等待.
接下来解释一下常见的 IO 模型:
阻塞 IO 模型 (最传统的 IO 模型): 在读写数据过程中会发生阻塞现象. 当用户线程发出 IO 请求之后, 内核会去查看数据是否就绪, 如果没有就绪就会等待数据就绪, 而用户线程就会处于阻塞状态, 用户线程交出 CPU. 当数据就绪之后, 并返回结果给用户线程, 用户线程才解除 block 状态.
典型的阻塞 IO 模型的例子为: data=socket.read(); 如果数据没有就绪, 就会一直阻塞在 read 方法.
非阻塞 IO 模型: 当用户线程发起 read 操作后, 并不需要等待, 而是马上就得到一个结果. 如果结果是 error 时, 它就知道数据还没有准备好, 于是它可以再次发送 read 操作. 一旦内核中的数据准备好了, 并且又再次收到了用户线程的请求, 那么它马上就将数据拷贝到用户线程, 然后返回.
在非阻塞 IO 模型中, 用户线程需要不断地询问内核数据是否就绪, 也就是说非阻塞 IO 不会交出 CPU, 而是一直占用 CPU.
典型的非阻塞 IO 模型一般如下:
- while(true){
- data = socket.read();
- if(data!= error){ // 处理数据
- break;
- }
- }
多路复用 IO 模型: NIO 实际上就是多路复用 IO. 在多路复用 IO 模型中, 会有一个线程不断去轮询多个 socket 的状态, 只有当 socket 真正有读写事件时, 才会调用实际的 IO 读写操作. 多路复用 IO 模式, 通过一个线程就可以管理多个 socket, 不需要建立新的进程或线程, 也不必维护这些线程和进程, 只有当 socket 真正有读写事件发生时才会占用资源来进行实际的读写操作. 所以他大大减少了资源占用. 因此, 多路复用 IO 适合连接数比较多的情况.
在 NIO 中, 通过 selector.select() 去查询每个通道是否有到达事件, 如果没有则一直阻塞在那里, 因此这种方式会导致线程阻塞.
多路复用 IO 为何比非阻塞 IO 的效率高?
答: 是因为在非阻塞 IO 中, 不断地去询问 socket 状态是通过用户线程去进行的, 而在多路复用 IO 中, 轮询每个 socket 状态是在内核进行的, 效率比用户线程要高. 不过要注意的是, 多路复用 IO 模型是通过轮询的方式来检测是否有事件到达, 并且对于到达的时间逐一进行响应. 因此对于多路复用 IO 模型来说, 一旦事件响应体很大, 那么就会导致后续的事件迟迟得不到处理并且会影响新的事件轮询.
信号驱动 IO 模型: 在信号驱动 IO 中, 当用户线程发起一个 IO 请求操作, 会给对应的 socket 注册一个信号数, 然后用户线程会继续执行, 当内核数据就绪时会发送一个信号给用户线程, 用户线程接收到信号后, 便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作.
异步 IO 模型: 异步 IO 模型是理想的 IO 模型. 在异步 IO 模型中, 当用户线程发起 read 操作后, 就可以开始去做别的事情. 另一方面, 从内核的角度, 当它收到一个 asynchronous read 之后, 它会立刻返回, 说明 read 请求已经成功发起了, 因此不会对用户线程产生任何 block. 然后内核会等待数据准备完成, 将数据拷贝到用户线程, 当这一切都完成后, 内核会给用户线程发送一个信号, 告诉它 read 操作完成了.
在异步 IO 模型中, IO 操作的两个阶段都不会阻塞用户线程, 这两个阶段都是由内核自动完成, 然后发送一个信号告知用户线程操作已经完成. 用户线程中不需要再次调用 IO 函数进行具体的读写.
来源: http://www.bubuko.com/infodetail-3259946.html