Jamal的博客

Linux-阻塞非阻塞,同步异步的区别

要理解这两个概念,首先我们做一个简单的比喻:

扒一个知乎上的比喻:

1
2
3
4
5
6
7
8
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。

这个比喻其实比较形象的比喻了非阻塞的过程,对同步异步的过程其实比喻的不是太好,我改进了一下:

1
2
3
4
5
6
7
8
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶),水壶。
1 老张把水壶放到火上,立等水开,水开了之后老张把水倒进水壶。(同步阻塞)
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有,水开了之后老张把水倒进水壶。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开,水开了之后老张把水倒进水壶。(异步阻塞)老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,水开了之后自动流进水壶,然后发出响声,老张直接拿水壶走人(异步非阻塞)
老张觉得自己聪明了。

好了,让我们回到权威,看看《UNIX网络编程》是怎么解释这几个概念的,摘自《UNIX网络编程1》第六章:
首先说一下Linux基本的IO模型:

  1. 阻塞式IO
  2. 非阻塞式IO
  3. IO复用(select poll epoll)
  4. 信号驱动式(SIGIO)
  5. 异步IO(POSIX的aio系列函数)
    正常的一个输入操作包括两个部分组成:
    (1)等待数据准备好;
    (2)从内核向进程复制数据;
    对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达,第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

    1. 阻塞式IO


    应用进程阻塞在标红的这部分。进程调用recvfrom,其系统调用知道数据报到达且被复制到应用进程的缓冲区中或者发生错误的时候才返回,我们说进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的,recvfrom成功返回后,应用进程开始处理数据报。

    2. 非阻塞式IO


    这句话很重要:进程把一个套接字设置成非阻塞是在通知内核:当所有的请求IO操作非得把这个进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误。
    当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮训(polling)。应用进程持续轮询内核,以查看某个操作是否就绪,这样的做法往往会消耗大量的CPU时间。
    在上图中,可以看到recvfrom总是立即返回并不阻塞,但是这时候从内核复制到用户进程还是同步的。

    3. IO复用(select poll epoll)

    !()[https://pic1.zhimg.com/b1ec6a4f16844a27c175d5a6a94cd7f8_b.jpg]
    IO复用也是阻塞的,这时候会阻塞在select poll这两个系统调用上,而不是阻塞在真正的IO调用上。
    阻塞在select调用,等待数据报套接字变成可读,当select返回套接字可读的时候,调用recvfrom把数据报复制到应用进程缓冲区,这时候读取数据的操作也是阻塞的。

    4. 信号驱动式(SIGIO)


    信号驱动先不细说。

    5. 异步IO(POSIX的aio系列函数)


    异步的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核缓冲区复制到应用进程缓冲区)完成后通知我们。这个模式和信号驱动的区别在于:信号驱动是内核通知我们什么时候可以启动一个IO操作,而异步IO是内核通知我们IO操作什么时候完成。

IO模型的比较

  1. 同步IO:导致请求进程阻塞,直到IO操作完成;
  2. 异步IO:不导致请求进程阻塞;
    可能会有人问:你是说了各种IO,但还是没有说明白阻塞非阻塞,同步异步的区别,别急,我们看看下面一幅图:

    实际上前面的四种IO模型,唯一的区别在于等待数据的过程中是怎么处理的,实际在从内核拷贝数据到用户进程的时候都是阻塞的,所以都是同步IO,只有最后一个异步IO模型才是完全异步的。
    总结一下:
    1
    2
    1. 阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;
    2. 同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。