POSIX标准。都没有提到过 哪两个名词,异步就是异步。只有同步时候才有 阻塞和非阻塞 的说法。
都tm异步了,还阻不阻塞个jb

在处理 IO 的时候,阻塞和非阻塞都是同步 IO。
只有使用了特殊的 API 才是异步 IO。

而且 io 的同步异步是用户态程序与内核交互的关系,像Tornado的则是application与框架之间交互的关系

libgpiod的非阻塞_libgpiod的非阻塞

还有 百度的这个,标题写着 异步非阻塞方式。但是下面的内容连 异步的字都没有

libgpiod的非阻塞_数据_02



进入文章




阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

  • 举例:
  1. 你打电话问书店老板有没有《分布式系统》这本书。
  2. 如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果
  3. 如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。

在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

同步与异步

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

  • 同步
  1. 就是在发出一个 调用 时,在没有得到结果之前,该调用就不返回。
  2. 一旦调用返回,就得到返回值了。
    换句话说,就是由调用者主动等待这个调用的结果。
  • 异步
  1. 调用 在发出之后,这个调用就直接返回了,所以没有返回结果。
  2. 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在 调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举个例子:

你打电话问书店老板有没有《分布式系统》这本书,

  • 如果是同步通信机制,书店老板会说,你稍等,”我查一下"。然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
  • 而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。
    然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

参考原址 https://www.zhihu.com/question/19732473/answer/20851256

根据Unix Network Programming的定义,同步是在IO过程中发生阻塞的情况,异步是在IO过程中不发生阻塞的情况,所以不存在异步阻塞

一下小故事的 异步阻塞非阻塞 知识为了库科普概念,不要混淆

老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

1 老张把水壶放到火上,等待水开。(同步阻塞)老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)老张还是觉得自己有点傻
	于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,等待水开。(异步阻塞)老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。
等待的老张,阻塞;看电视的老张,非阻塞。
情况1 和情况3 中老张就是阻塞的,媳妇喊他都不知道。
虽然3 中响水壶是异步的,可对于立等的老张没有太大的意义。

所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

来源网络,作者不详
虽然这个小故事中说出了 异步阻塞 异步非阻塞,但只是为了科普。异步阻塞这个场景 我不相信有人用过,写在这里只是为了科普一下。而且 你觉得 老张会傻乎乎的等待着响水壶开吗?异步的话就没有阻塞/非阻塞的概念了,因为水开了之后,它会callback你

总结:
阻塞和 同步很像,但 从大的概念上来讲同步和异步关注的是消息通信机制(因为 阻不阻塞只有同步才会出现),阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

了解完了 这些基本概念,我们再加上 IO的思想来理解


由于网络上大家对 此文话题都各有见解,所以 请查看权威地址
《UNIX网络编程:卷一》https://book.douban.com/subject/1500149/ 大家自行查找 《UNIX网络编程:卷一》第六章——I/O复用。

用户空间和内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。

为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

针对linux操作系统而言

  • 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,
  • 而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
缓存IO

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

  • 缓存 I/O 的缺点:
    数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

  1. 等待数据准备 (Waiting for the data to be ready)
  2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

此时 也对应了下面的阻塞 IO

前言:

  • 同步io指的是由应用程序主动完成io操作,异步io指的是由内核在后台完成io操作
  • 同步io有阻塞,非阻塞,信号驱动io,io复用,这四种。
select,poll,epoll

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。现在比较出名的nginx就是使用epoll来实现I/O复用支持高并发

但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

文件描述符(File descriptor)

  1. select==>时间复杂度O(n)
    fd_set(监听的端口个数):32位机默认是1024个,64位机默认是2048。
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),
我们只能无差别轮询所有流(fd_set),找出能读出数据,或者写入数据的流,对他们进行操作。
所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构
  1. poll==>时间复杂度O(n)
    采用链表的方式替换原有fd_set数据结构,而使其没有连接数的限制。
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,
 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
  1. epoll==>时间复杂度O(1)
    既没有限制链接,epoll会把哪个流发生了怎样的I/O事件通知我们。
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。
所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对"活跃"的socket进行操作

libgpiod的非阻塞_libgpiod的非阻塞_03

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现





同步

阻塞式I/O模型

默认情况下,所有套接字都是阻塞的。怎么理解?先理解这么个流程,一个输入操作通常包括两个不同阶段:

  1. 等待数据准备好;
  2. 从内核向进程复制数据。

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。

libgpiod的非阻塞_数据_04


标红的这部分过程就是阻塞,直到阻塞结束recvfrom才能返回。

非阻塞式I/O:

进程把一个套接字设置成非阻塞是在通知内核,当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误。看看非阻塞的套接字的recvfrom操作如何进行

libgpiod的非阻塞_非阻塞_05


可以看出recvfrom总是立即返回。

I/O多路复用

虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。如图

libgpiod的非阻塞_libgpiod的非阻塞_06

信号驱动式I/O

用的很少,就不做讲解了。直接上图

libgpiod的非阻塞_应用程序_07


异步

异步I/O

这类函数的工作机制是告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到用户空间)完成后通知我们。

libgpiod的非阻塞_libgpiod的非阻塞_08


注意红线标记处说明在调用时就可以立马返回,等函数操作完成会通知我们。


POSIX对这两个术语的定义:

  • 同步I/O操作:导致请求进程阻塞,直到I/O操作完成;
  • 异步I/O操作:不导致请求进程阻塞。

阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;

区别: 数据拷贝的时候进程是否阻塞

同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。

区别: 应用程序的调用是否立即返回

大家不要把epoll的实现机制和真正的IO两个概念弄混淆了。epoll相对select和poll而言的一个优势,它无需将感兴趣的事件从用户空间拷贝至内核空间,当然事件就绪后内核检查之后传出到用户空间还是需要拷贝的。后续调用recv/send如果不把套接字设置为非阻塞还是会阻塞啊.