返回 Unix 网络...... 登陆

Unix 网络 IO 模型: 同步异步, 傻傻分不清楚?

高洛峰 2016-11-05 15:51:37 482

阻塞式 I/O 模型

最流行的 IO 操作是阻塞式 IO(Blocking IO). 以 UDP 数据报套接字为例, 下图是其阻塞 IO 的调用过程:

1.png

在上图中, 进程调用 recvfrom, 其系统调用直到数据报返回并且被复制到应用进程的缓冲区中 或者发送错误时才返回. 因此进程在调用 recvfrom 开始到它返回的整段时间内都是被阻塞的.

非阻塞式 IO(Non-Blocking IO)

进程把一个套接字设置为非阻塞是在通知内核: 当调用线程所请求的 IO 操作需要调用线程休眠来等待操作完成时, 此时不要将调用线程休眠, 而是返回一个错误.

1.png

如上图所示, 前三次调用 recvfrom 时, 没有数据可返回, 因此内核转而立即返回一个 EWOULDBLOCK 错误. 第四次调用 recvfrom 时, 已经有数据了, 此时, recvfrom 会阻塞住, 等待内核将数据赋值到应用进程的缓冲区中, 然后再返回.(注意, 当有数据时, recvfrom 是阻塞的, 它会等待内核将数据复制到应用进程的缓冲区后, 才返回).
当一个应用进程像这样对一个非阻塞描述符循环调用 recvfrom 时, 我们称之为轮询(polling). 应用进程持续轮询内核, 以查看某个操作是否完成, 这么做会消耗大量的 CPU 时间, 不过这种模型偶尔也会遇到, 通常是专门提供某一种功能的系统中才有.

IO 复用模型

有了 IO 复用(IO multiplexing), 我们就可以调用 select 或 poll, 阻塞在这两个系统调用中的某一个之上, 而不是阻塞在真正的 IO 系统调用上. 例如:

1.png

如上图所示, 前三次调用 recvfrom 时, 没有数据可返回, 因此内核转而立即返回一个 EWOULDBLOCK 错误. 第四次调用 recvfrom 时, 已经有数据了, 此时, recvfrom 会阻塞住, 等待内核将数据赋值到应用进程的缓冲区中, 然后再返回.(注意, 当有数据时, recvfrom 是阻塞的, 它会等待内核将数据复制到应用进程的缓冲区后, 才返回).
当一个应用进程像这样对一个非阻塞描述符循环调用 recvfrom 时, 我们称之为轮询(polling). 应用进程持续轮询内核, 以查看某个操作是否完成, 这么做会消耗大量的 CPU 时间, 不过这种模型偶尔也会遇到, 通常是专门提供某一种功能的系统中才有.

IO 复用模型

有了 IO 复用(IO multiplexing), 我们就可以调用 select 或 poll, 阻塞在这两个系统调用中的某一个之上, 而不是阻塞在真正的 IO 系统调用上. 例如:

1.png

信号驱动模型如上图所示. 当文件描述符就绪时, 我们可以让内核以信号的方式通知我们.我们首先需要开启套接字的信号驱动式 IO 功能, 并通过 sigaction 系统调用安装一个信号处理函数. sigaction 系统调用是异步的, 它会立即返回. 当有数据时, 内核会给此进程发送一个 SIGIO 信号, 进而我们的信号处理函数就会被执行, 我们就可以在这个函数中调用 recvfrom 读取数据.

异步 IO 模型

异步 IO (asynchronous IO) 由 POSIX 规范定义, 在 POSIX 中定义了若干个异步 IO 的操作函数. 这个函数的工作原理是: 告知内核启动某个动作, 并让内核在整个操作(包括将数据从内核复制到应用进程缓冲区)完成后通知我们的应用进程.
异步 IO 模型和信号驱动的 IO 模型的主要区别在于: 信号驱动 IO 是由内核通知我们何时可以启动一个 IO 操作, 而异步 IO 模型是由内核通知我们 IO 操作何时完成.
异步 IO 模型的操作过程如图所示:

1.png

当我们调用 aio_read 函数时(POSIX 异步 IO 函数以 aio_或 lio_ 开头), 给内核传递描述符, 缓冲区指针, 缓冲区大小(和 read 相同的三个参数) 和文件偏移(以 lseek 类似), 并告诉内核当整个操作完成时如何通知应用进程. 该系统调用立即返回, 而且在等待 IO 完成期间, 应用进程不被阻塞.

各种 IO 模型的比较

1.png

如图所示, 上述五中 IO 模型中, 前四种模型(阻塞 IO, 非阻塞 IO, IO 复用, 信号驱动 IO)的主要区别在于第一阶段, 因为他们的第二阶段是一样的: 在数据从内核复制到调用者的缓冲区期间, 进程阻塞于 recvfrom 调用. 而第五种, 即异步 IO 模型中, 两个阶段都不需要应用进程处理, 内核为我们处理好了数据的等待和数据的复制过程.

关于同步 IO 和异步 IO

根据 POSIX 定义:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes(导致请求进程阻塞, 直到 IO 操作完成).

An asynchronous I/O operation does not cause the requesting process to be blocked(不导致请求进程阻塞).
根据上述定义, 我们的前四种模型: 阻塞 IO 模型, 非阻塞 IO 模型, IO 复用模型和信号驱动 IO 模型都是同步 IO 模型, 因为其中真正的 IO 操作(recvfrom 调用) 会阻塞进程(因为当有数据时, recvfrom 会阻塞等待内核将数据从内核空间复制到应用进程空间, 当赋值完成后, recvfrom 才返回.) 只有异步 IO 模型与 POSIX 定义的异步 IO 相匹配.

总结

在处理网络 IO 操作时, 阻塞和非阻塞 IO 都是同步 IO.只有调用了特殊的 API 才是异步 IO.

1.png

因此网上常说的 "同步阻塞 IO", "同步非阻塞 IO" 其实就是阻塞 IO 模型和非阻塞 IO 模型, 因为阻塞 IO 和非阻塞 IO 模型都是同步的, 加了 "同步" 二字其实是多余了.网络上常说的 "异步非阻塞 IO" 其实就是异步 IO 模型.

最新手记推荐

• 用composer安装thinkphp框架的步骤 • 省市区接口说明 • 用thinkphp,后台新增栏目 • 管理员添加编辑删除 • 管理员添加编辑删除

全部回复(0)我要回复

暂无评论~
  • 取消 回复 发送
  • PHP中文网