极客时间已完结课程限时免费阅读

32 | 答疑(四):阻塞、非阻塞 I/O 与同步、异步 I/O 的区别和联系

32 | 答疑(四):阻塞、非阻塞 I/O 与同步、异步 I/O 的区别和联系-极客时间

32 | 答疑(四):阻塞、非阻塞 I/O 与同步、异步 I/O 的区别和联系

讲述:冯永吉

时长07:03大小6.47M

你好,我是倪朋飞。
专栏更新至今,四大基础模块的第三个模块——文件系统和磁盘 I/O 篇,我们就已经学完了。很开心你还没有掉队,仍然在积极学习思考和实践操作,并且热情地留言与讨论。
今天是性能优化的第四期。照例,我从 I/O 模块的留言中摘出了一些典型问题,作为今天的答疑内容,集中回复。同样的,为了便于你学习理解,它们并不是严格按照文章顺序排列的。
每个问题,我都附上了留言区提问的截屏。如果你需要回顾内容原文,可以扫描每个问题右下方的二维码查看。

问题 1:阻塞、非阻塞 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 响应的通知方式的不同,可以把文件 I/O 分为同步 I/O 和异步 I/O。
所谓同步 I/O,是指收到 I/O 请求后,系统不会立刻响应应用程序;等到处理完成,系统才会通过系统调用的方式,告诉应用程序 I/O 结果。
所谓异步 I/O,是指收到 I/O 请求后,系统会先告诉应用程序 I/O 请求已经收到,随后再去异步处理;等处理完成后,系统再通过事件通知的方式,告诉应用程序结果。
你可以看出,阻塞 / 非阻塞和同步 / 异步,其实就是两个不同角度的 I/O 划分方式。它们描述的对象也不同,阻塞 / 非阻塞针对的是 I/O 调用者(即应用程序),而同步 / 异步针对的是 I/O 执行者(即系统)。
我举个例子来进一步解释下。比如在 Linux I/O 调用中,
系统调用 read 是同步读,所以,在没有得到磁盘数据前,read 不会响应应用程序。
而 aio_read 是异步读,系统收到 AIO 读请求后不等处理就返回了,而具体的 read 结果,再通过回调异步通知应用程序。
再如,在网络套接字的接口中,
使用 send() 直接向套接字发送数据时,如果套接字没有设置 O_NONBLOCK 标识,那么 send() 操作就会一直阻塞,当前线程也没法去做其他事情。
当然,如果你用了 epoll,系统会告诉你这个套接字的状态,那就可以用非阻塞的方式使用。当这个套接字不可写的时候,你可以去做其他事情,比如读写其他套接字。

问题 2:“文件系统”课后思考

文件系统原理文章的最后,我给你留了一道思考题,那就是执行 find 命令时,会不会导致系统的缓存升高呢?如果会导致,升高的又是哪种类型的缓存呢?
关于这个问题,白华和 coyang 的答案已经很准确了。通过学习 Linux 文件系统的原理,我们知道,文件名以及文件之间的目录关系,都放在目录项缓存中。而这是一个基于内存的数据结构,会根据需要动态构建。所以,查找文件时,Linux 就会动态构建不在缓存中的目录项结构,导致 dentry 缓存升高。
事实上,除了目录项缓存增加,Buffer 的使用也会增加。如果你用 vmstat 观察一下,会发现 Buffer 和 Cache 都在增长:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 1 0 7563744 6024 225944 0 0 3736 0 574 3249 3 5 89 3 0
1 0 0 7542792 14736 236856 0 0 8708 0 13494 32335 8 19 66 7 0
0 1 0 7494452 27280 272284 0 0 12544 0 4550 17084 5 15 68 13 0
0 1 0 7475084 42380 276320 0 0 15096 0 2541 14253 2 6 78 13 0
0 1 0 7455728 57600 280436 0 0 15220 0 2025 14518 2 6 70 22 0
这里,Buffer 的增长是因为,构建目录项缓存所需的元数据(比如文件名称、索引节点等),需要从文件系统中读取。

问题 3:“磁盘 I/O 延迟”课后思考

磁盘 I/O 延迟案例的最后,我给你留了一道思考题。
我们通过 iostat ,确认磁盘 I/O 已经出现了性能瓶颈,还用 pidstat 找出了大量磁盘 I/O 的进程。但是,随后使用 strace 跟踪这个进程,却找不到任何 write 系统调用。这是为什么呢?
很多同学的留言都准确回答了这个问题。比如,划时代和 jeff 的留言都指出,在这个场景中,我们需要加 -f 选项,以便跟踪多进程和多线程的系统调用情况。
你看,仅仅是不恰当的选项,都可能会导致性能工具“犯错”,呈现这种看起来不合逻辑的结果。非常高兴看到,这么多同学已经掌握了性能工具使用的核心思路——弄清楚工具本身的原理和问题。

问题 4:“MySQL 案例”课后思考

MySQL 案例的最后,我给你留了一个思考题。
为什么 DataService 应用停止后,即使仍没有索引,MySQL 的查询速度还是快了很多,并且磁盘 I/O 瓶颈也消失了呢?
ninuxer 的留言基本解释了这个问题,不过还不够完善。
事实上,当你看到 DataService 在修改 /proc/sys/vm/drop_caches 时,就应该想到前面学过的 Cache 的作用。
我们知道,案例应用访问的数据表,基于 MyISAM 引擎,而 MyISAM 的一个特点,就是只在内存中缓存索引,并不缓存数据。所以,在查询语句无法使用索引时,就需要数据表从数据库文件读入内存,然后再进行处理。
所以,如果你用 vmstat 工具,观察缓存和 I/O 的变化趋势,就会发现下面这样的结果:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
# 备注: DataService正在运行
0 1 0 7293416 132 366704 0 0 32516 12 36 546 1 3 49 48 0
0 1 0 7260772 132 399256 0 0 32640 0 37 463 1 1 49 48 0
0 1 0 7228088 132 432088 0 0 32640 0 30 477 0 1 49 49 0
0 0 0 7306560 132 353084 0 0 20572 4 90 574 1 4 69 27 0
0 2 0 7282300 132 368536 0 0 15468 0 32 304 0 0 79 20 0
# 备注:DataService从这里开始停止
0 0 0 7241852 1360 424164 0 0 864 320 133 1266 1 1 94 5 0
0 1 0 7228956 1368 437400 0 0 13328 0 45 366 0 0 83 17 0
0 1 0 7196320 1368 470148 0 0 32640 0 33 413 1 1 50 49 0
...
0 0 0 6747540 1368 918576 0 0 29056 0 42 568 0 0 56 44 0
0 0 0 6747540 1368 918576 0 0 0 0 40 141 1 0 100 0 0
在 DataService 停止前,cache 会连续增长三次后再降回去,这正是因为 DataService 每隔 3 秒清理一次页缓存。而 DataService 停止后,cache 就会不停地增长,直到增长为 918576 后,就不再变了。
这时,磁盘的读(bi)降低到 0,同时,iowait(wa)也降低到 0,这说明,此时的所有数据都已经在系统的缓存中了。我们知道,缓存是内存的一部分,它的访问速度比磁盘快得多,这也就能解释,为什么 MySQL 的查询速度变快了很多。
从这个案例,你会发现,MySQL 的 MyISAM 引擎,本身并不缓存数据,而要依赖系统缓存来加速磁盘 I/O 的访问。一旦系统中还有其他应用同时运行,MyISAM 引擎就很难充分利用系统缓存。因为系统缓存可能被其他应用程序占用,甚至直接被清理掉。
所以,一般来说,我并不建议,把应用程序的性能优化完全建立在系统缓存上。还是那句话,最好能在应用程序的内部分配内存,构建完全自主控制的缓存,比如 MySQL 的 InnoDB 引擎,就同时缓存了索引和数据;或者,可以使用第三方的缓存应用,比如 Memcached、Redis 等。
今天主要回答这些问题,同时也欢迎你继续在留言区写下疑问和感想,我会持续不断地解答。希望借助每一次的答疑,可以和你一起,把文章知识内化为你的能力,我们不仅在实战中演练,也要在交流中进步。
分享给需要的人,Ta购买本课程,你将得20
生成海报并分享

赞 22

提建议

上一篇
31 | 套路篇:磁盘 I/O 性能优化的几个思路
下一篇
33 | 关于 Linux 网络,你必须知道这些(上)
unpreview
 写留言

精选留言(26)

  • eagle
    2019-03-26
    我根据我们自己实际应用中遇到的情况,试着回复一下两个问题: 安小依 的问题,df -h 显示占用100%,而关闭应用程序后,再次df -h是85%,这一般是因为该应用程序还有指向已删除文件的文件指针没有关闭,典型的比如日志文件,虽然在操作系统中用rm命令删除了,在相应的目录中已经没有该文件了,但如果应用中还有对应的文件指针没有关闭,则实际硬盘空间还不会释放,而应用程序被关闭时,实际空间才会释放。问题中更像是有些apk文件或处理后文件的文件指针没有释放。这种情况也可以通过 lsof | grep deleted 来找到这些文件。 lvy的out of memory的问题,可以先用free或top看一下可用内存是否确实没有了,如果确实是没有内存了,那再去研究内存的问题;还有一种常见情况,内存是充足的,文件描述符的个数或进程数达到上限了,那就得调整 ulimit,可以通过 ulimit -a (注意要用php的用户)来查看,关注open files和max user processes,这两个默认很小,1k和4k,建议调整到加两个0.
    展开

    作者回复: 谢谢😊

    共 4 条评论
    19
  • ninuxer
    2019-02-01
    打卡day33 感恩作者带来的分享,提前祝新年快乐!

    作者回复: 新年快乐!

    共 2 条评论
    8
  • Maxwell
    2019-03-14
    Windows和linux有很大区别吧?如果想深入了解windows,有什么可以推荐的书吗?

    作者回复: Windows书籍最推荐的是《Windows Internals 7th edition》

    7
  • Ivy
    2019-02-19
    老师您好,我最近在生产环境遇到一个问题,centos7频繁报错tcp out of memory ,访问页面时css文件响应头200,但是响应正文为空,我猜测就是因为tcp问题,有时候又能正常返回,每次重启php fpm就能解决问题,cat /proc/net/sockstat 的时候tcp 行mem值在fpm重启前后差距很大,同时tw状态的连接也很多,alloc也很大,我该怎么去找原因?能看到每个tw状态的连接占用多少tcp 内存吗?或者怎么查询php fpm为何没有释放tcp内存?
    展开

    作者回复: 可以考虑调整 tcp_max_tw_buckets、ip_conntrack_max、ip_conntrack 这些内核选项

    5
  • Stephen
    2019-04-03
    《UNIX网络编程》提到的5种IO模型中,除了异步IO模型没有阻塞操作外,其他四种IO模型(阻塞IO、非阻塞IO、IO多路复用、信号驱动IO)都有阻塞操作。是不是可以这么理解: 同步IO一定有阻塞可能有非阻塞, 异步IO一定是非阻塞; 有阻塞一定是同步IO, 有非阻塞可能是同步IO或异步IO 求大佬解答问题
    展开
    共 1 条评论
    1
  • 没有昵称
    2020-08-01
    我觉得同步异步io的提问者并不是想要理解字面意思,而是想要了解内部的工作模式,之前看过一篇文章讲解的比较好,把io分成了几个阶段,不同类型的io每个阶段都干了什么,回头再找下

    作者回复: 欢迎分享一下链接

    共 3 条评论
    2
  • LA
    2019-06-01
    老师,看了您的文章,有个问题一直在困扰这我。文章所说进程不可中断状态有可能是因为等待io响应,那这里的等待io响应包括等待从套接字读取数据么?如果是包括的话对于阻塞io来讲岂不是只要有阻塞进程就一直处在不可中断状态,从而无法被kill信号杀掉?

    作者回复: 不包括套接字

    2
  • allan
    2019-03-24
    原文:DataService 停止后,bi iowait 都降到0,说明此时的所有数据都已经在系统的缓存中了。 这里所有数据指的是 数据库文件 中的数据 是吗?不包括索引。

    作者回复: 嗯,数据库文件

    1
  • 安小依
    2019-03-01
    老师,今天遇见了一个问题: 系统使用 df -h 显示磁盘占用100%了,而且应用程序(这是一个不停下载 apk 文件、解压缩并分析 apk文件的应用程序)在命令行也提示磁盘空间不足了。但是,关闭应用程序后,再次 df-h 统计,却发现这次磁盘占用是 85%,释放了 15%大约150G 的空间…能大概推测出来为什么关闭应用后,磁盘空间突然多了的原因吗?

    作者回复: 解压缩很可疑,有没有看看这些apk解压后的大小?

    共 2 条评论
    1
  • Leon📷
    2019-02-01
    老师,读文件系统的内容不会引起buffer升高吧,读块设备会引起,我做了文章的实验发现 r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 1620788 0 431512 0 0 348 70 415 585 2 4 94 0 0 0 0 0 1618488 0 431948 0 0 480 0 1605 2056 1 4 96 0 0 0 0 0 1619524 0 431788 0 0 16 0 1157 1674 1 2 97 0 0 2 0 0 1499696 0 548464 0 0 116905 281 5084 7062 3 14 82 1 0 2 0 0 1495664 0 552444 0 0 4964 125 2996 4413 2 6 92 0 0 2 0 0 1329960 0 646564 0 0 34028 0 8495 10589 21 24 53 1 0 2 0 0 1152440 0 769524 0 0 142805 206 13584 16541 19 32 48 1 0 3 0 0 1112028 0 783200 0 0 44753 86 14794 20490 19 23 57 0 0 0 0 0 1050900 0 809624 0 0 36540 0 8927 13517 5 20 75 0 0 0 0 0 1050892 0 809636 0 0 0 0 1277 1879 1 2 97 0 0 0 0 0 1050632 0 809644 0 0 0 0 1344 1953 1 2 97 0 0 buffer并没有升高
    展开

    作者回复: 是的,读文件内容的时候不会的。文中指的是执行 find 命令查找文件的情景

    1
  • Peter
    2022-04-17
    阻塞、非阻塞 I/O 以及同步、异步 I/O的区别和 同步、异步、阻塞、非阻塞之间区别是不是同一个概念的? 我之前看过还有同步阻塞、同步非阻塞、异步阻塞、异步非阻塞等
  • 谛听
    2022-02-08
    有个疑问,rust 或者 node.js 或者 python 中的 async/await 是同步的还是异步的,是阻塞的还是非阻塞的?有 async 关键字,应该是异步的,可是会等待 await 返回结果后才会继续执行,又觉得是同步的。因为会等待 await 返回结果后才往下走,应该是阻塞的,但实际上其它的 future 也在执行,又不是阻塞的,有点混乱,希望能解答一下
  • 2021-12-20
    - 阻塞/非阻塞:针对的是数据未就绪操作是否能够立即返回的划分。非阻塞调用后立马返回,随后通过轮询或者事件得知数据是否就绪 - 同步/异步:针对的数据就绪后的获取/写入操作过程是否立刻返回的划分。同步操作有用户线程同步完成,异步操作(比如read)由于用户线程需要立刻返回,数据拷贝的操作由内核完成。
  • 笃定
    2021-08-07
    老师,有一个疑问,如果我的应用程序使用的是异步非阻塞IO调用方式,那么我发起IO请求获取大量的数据,因为是异步非阻塞的;可以不马上得到数据而继续执行其他任务,这样的话,是不是我这个系统上就不会出现CPUIOWAIT升高的情况呢(系统上就只跑这一个程序的情况下)
    1
  • curry30
    2021-06-15
    老师你好,文中说的“Buffer 的增长是因为,构建目录项缓存所需的元数据(比如文件名称、索引节点等),需要从文件系统中读取”,这里“文件系统中读取”描述有点疑惑,文件系统中读取的话,增长的cache,磁盘中读取的话才是增长Buffer吧,不是很能理解。
    共 1 条评论
  • 奋斗
    2021-06-10
    我觉得同步io和异步io最大区别是:同步io需要用户线程去内核主动读取数据,异步io是内核已经将数据返回给用户,用户直接使用即可
  • Geek_9a0180
    2021-05-04
    老师您好,不是很懂文中说的“Buffer 的增长是因为,构建目录项缓存所需的元数据(比如文件名称、索引节点等),需要从文件系统中读取”,是元数据都存在Buffer中吗?望解答
  • Cryhard
    2020-12-19
    复习一下,仍然会有新的收获!谢谢!
    1
  • 青鸟飞鱼
    2020-11-07
    老师你好,将数据从内核空间复制到用户空间这个过程中,如果这个过程中应用程序被阻塞就叫同步IO,否则就叫异步IO。这种说法对吗?
  • Jarvis
    2019-07-29
    他如果都从调用者的角度来看,阻塞和同步其实没什么区别?都是在得到响应之前没法做其他事?
    共 1 条评论