抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

同步与异步

同步和异步描述的是用户线程和内核的交互方式。

  • 同步: 指的是在两个或多个数据库、文件、模块、线程之间用来保持数据内容一致性的机制。用户线程发起I/O请求后需要等待,或者轮询内核I/O操作完成后才能继续执行。

    同步处理过程:提交请求->等待内核处理(期间该线程不能干任何事)->处理完毕返回

  • 异步: 指用户线程发起I/O请求后仍继续执行,不用阻塞当前线程来等待处理完成,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数,回调通知此线程。

    异步处理过程:请求通过事件触发->内核处理(这时该线程仍然可以作其他事情)->处理完毕

同步、异步关注的是消息通知机制,针对的是客户端。

阻塞与非阻塞

阻塞和非阻塞描述的是用户线程调用内核I/O操作的方式

  • 阻塞:调用结果返回之前,调用者会被挂起(不可中断),调用者只有在得到返回结果后才能继续。即I/O操作需要彻底完成后才返回用户空间。
  • 非阻塞:调用结果返回前,不会被挂起,调用不会阻塞调用者。在内核的数据还未准备好时,会立即返回,进程可以去干其他事情。即I/O操作被调用后立即返回给用户一个状态值,无须等到I/O操作彻底完成。

阻塞、非阻塞关注的是调用者等待被调用者返回结果时的状态,针对的是服务器端。

阻塞、非阻塞与同步、异步的区别

一个I/O操作实际是分为两个步骤:发起I/O请求和实际的I/O请求。

  • 阻塞和非阻塞在于第一步,即发起I/O请求是否被阻塞。
  • 同步和非同步在于第二步,即实际I/O请求是否被阻塞。

同步是在 I/O 中的一系列操作都是调用者(用户进程)自己完成(自己去问内核)。而异步是调用者在发起调用后,自己不管了,等内核数据准备好了以后,内核自己告诉进程,即让内核去通知进程,实现回调。

至于阻塞与非阻塞,是决定是否让调用者挂起。

网络 I/O 的本质是 socket 的读取,socket 在 linux 系统被抽象为流,I/O 可以理解为对流的操作。这个操作又分为两个阶段:

1.等待流数据准备,即等待网络上的数据分组到达,然后被复制到内核的某个缓冲区
2.从内核向进程复制数据,把数据从内核缓冲区复制到应用进程缓冲区

五种 Unix I/O 模型

I/O 模型:
进程是无法直接操作 I/O 设备的,其必须通过系统调用请求内核来协助完成 I/O 动作,而内核会为每个 I/O 设备维护一个 buffer。 用户进程发起请求,内核接受到请求后,从 I/O 设备中获取数据到 buffer 中,再将 buffer 中的数据 copy 到用户进程的地址空间,该用户进程获取到数据后再响应客户端。
如下图中,真正称为 I/O 的就是内核内存与与进程内存间的过程

同步 I/O 模型

阻塞 I/O(Blocking I/O): 当用户进程进行系统调用 read()时,进程发起 recvform 系统调用,内核就开始了 I/O 的第一个阶段,准备数据到缓冲区中,当数据都准备完成后,则将数据从内核缓冲区中拷贝到用户进程的内存中,这时用户进程才解除 block 的状态重新运行。整个过程中用户进程都是阻塞的。不会消耗 CPU 时间,执行效率高。

非阻塞 I/O(Non-Blocking I/O): 用户进程只有在第二个阶段被阻塞了,而第一个阶段没有阻塞。在第一个阶段中,recvform 系统调用调用之后,内核马上返回给进程,如果数据还没准备好,此时会返回一个 error,进程在返回之后,可以干点别的事情,然后再发起 recvform 系统调用,用户进程需要盲等,不停的去轮询内核,看数据是否准备好了。在拷贝数据整个过程,进程仍然是属于阻塞的状态。由于用户进程轮询内核,所以该模型是比较消耗 CPU 的,效率较低。

** I/O 复用(I/O Multiplexing):** I/O 执行的两个阶段都是用户进程都是阻塞的,但是两个阶段是独立的,在一次完整的 I/O 操作中,该用户进程是发起了两次系统调用。使用 select()、poll()或 epoll()(poll 的改进版)进行调用,可支持两路调用。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。

select 调用是内核级别的,select 轮询可以等待多个 socket,当其中任何一个 socket 的数据准备好了(通过内核监视),就能返回进行可读,然后进程再进行 recvform 系统调用。select 在此模式下最多只支持 1024 个并发。

I/O 复用应用场景:

  • 服务器需要同时处理多个处于监听状态或多个连接状态的套接字。
  • 服务器需要同时处理多种网络协议的套接字。

信号驱动 I/O(Signal Driven I/O): 也称基于事件的 I/O。只有在 I/O 执行的第二阶段阻塞了用户进程,而在第一阶段是没有阻塞的。在 I/O 执行的第一阶段,当数据准备完成之后,内核会主动的通知用户进程数据已经准备完成(通过返回一个 SIGIO 信号),即对用户进程做一个回调。该通知分为两种,一为水平触发,即如果用户进程不响应则会一直发送通知,二为边缘触发,即只通知一次。

注:需要先开启套接字的信号驱动 I/O 功能,并使系统调用 sigaction 安装一个信号处理函数

异步 I/O 模型

异步 I/O(Asynchrnous I/O): 当用户进程发起系统调用后,立刻就可以开始去做其它的事情,然后直到 I/O 执行的两个阶段都完成之后,内核会给用户进程发送通知,告诉用户进程操作已经完成了。由于在调用后进程会立刻返回,所以在整个输入操作的等待和复制期间,进程都不会阻塞。

异步 I/O 不需要 select 或 poll 主动询问,也没有询问描述符的数量限制。

参考文章:
简明网络 I/O 模型—同步异步阻塞非阻塞之惑

浅谈 Linux 下的五种 I/O 模型

Linux 网络 I/O 模型简介(图文)

socket 和 网络 I/O 模型