I/O 模型 02 - 同步阻塞 I/O

I/O 模型 02 - 同步阻塞 I/O

I/O 等待

说到阻塞,首先得说说 I/O 等待。 造成等待的原因非常多,比如 Web 服务器在等待用户的访问,这便是等待,因为它不知道谁会来访问,所以只能等。
随后,当某个用户通过浏览器发出请求,Web 服务器与该浏览器建立 TCP 连接后,又要等待用户发出 HTTP 请求数据,
用户的请求数据在网络上传输需要时间,进入服务器接收缓冲区队列及被复制到进程地址空间都需要时间。
另外,假如浏览器和 Web 服务器采用 HTTP 长连接模式,那么在超时关闭连接之前,服务器还要等待浏览器发送的其他请求,这也是 I/O 等待。

再比如,读取磁盘上某个文件的 I/O 操作,可能先要等待其他的磁盘访问操作,因为磁头的数量是有限的,所以只能一个个排队读取, 即使轮到了自己,在磁盘上读取数据本身也要花费时间。
值得一提的是,对 RAID 磁盘的某些规格(如 RAID 0),通过磁盘阵列将数据分布在多个磁盘上,大大提高了磁盘访问吞吐率。

可见,I/O 等待是不可避免的,那么既然有了等待,就会有阻塞,
但是注意,我们说的阻塞是指当前发起 I/O 操作的进程被阻塞,并不是 CPU 被阻塞, 事实上并没有什么能让 CPU 阻塞的,CPU 只知道拼命地计算,对于阻塞一无所知。

另外,“同步”的概念在这里显得并不那么重要,只是为了和后面的异步 I/O 加以区分, 我们在介绍异步事件通知和异步 I/O 的时候再来近探讨同步和异步的区别。
但是需要说明的是,对于磁盘文件的访问,也有一个所谓“同步”的选项,即使用 O_SYNC 标志打开文件。
在规范情况下,对磁盘文件调用 read() 将阻塞进程,一直到数据被复制到用户态内存空间,
而对磁盘文件调用 write() 则不同,它会在数据被复制到内核缓冲区后立即返回。 如果使用 O_SYNC 标志打开文件,则对写文件操作产生影响,它使得 write() 必须等待数据真正写入磁盘后才返回。

同步阻塞 I/O

同步阻塞 I/O 是指当进程调用某些涉及 I/O 操作的系统调用或库函数时,
比始 accept()/send()/recv() 等,进程便暂停下来,等待 I/O 操作完成后再继续运行。
这是一种简单而有效的 I/O 模型,它可以和多进程结合起来有效地利用 CPU 资源,但是其代价就是多进程的大量内存开销。

比如在 Apache prefork 模型中,某个子进程在等待请求时,进程阻塞在 semop() 调用,我们用 strace 进行跟踪,如下所示:


[root@VM_15_187_centos ~]# strace -p 20631
Process 20631 attached
semop(98307, 0, 1

可以看出,semop() 在等待用户连接的到达,同时该进程停在此处成为阻塞状态。

为了简单说明同步阻塞 I/O 以及其他 I/O 模型的区别,我们举个有意思的例子。

比如你去逛街,逛着逛着有点饿了,这时你看到商场里有小吃城,就去一个小吃店买一碗面条, 交了钱,可面条做起来得需要时间,你也不知道时候可以做好,没办法,只好坐在那里等,等面条做好后吃完再继续逛街。 显然,这里的吃面条便是 I/O 操作,它要等待厨师做面条,还要等待自己把面条吃完。

Ref

摘自《构建高性能 Web 站点》第 3 章 服务器并发处理能力 3.6 I/O 模型