简单来说,在应用中,阻塞、非阻塞和IO复用都是同步,异步是非阻塞的(严格来说异步也有阻塞的)。
同步/异步
关注的是一种消息通信的机制,关注的是手段与行动,表示一种协作方式,从全局或者更高的角度来看待“进程之间合作方式”的
同步:
总是按照“甲方请求一次,乙方应答一次”这样的有序序列处理业务,只有当“一次请求一次应答”的过程结束才可以发生下一次的“一次请求一次应答”,那么就说他们采用的是同步。(同步IO中,对同一个描述符的操作必须是有序的)也就是必须一件一件事做,等前一件做完了才能做下一件事。
异步:
阻塞/非阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。
非阻塞:
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
总结1:
阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;
同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。
同步与异步是针对应用程序与内核的交互而言的。同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。
总结2:
同步异步针对的是两个事情之间的关系,这种关系与拓扑结构中的先后关系类似类,如果两件事情之间存在拓扑关系,便是同步关系;如果没有时序先后的关联或相互依赖,则是异步关系。而阻塞非阻塞针对的是发起任务的人(线程)的状态问题。在I/O中,同步异步涉及的两件事情是:消息响应和消息处理,I/O模型中这两个事务之间的拓扑关系决定了模型是同步还是异步的。而在消息响应的过程中,发出I/O请求的线程所处的状态决定了模型是阻塞还是非阻塞的。
参考:
知乎用户@ 银月游侠
从三个层次理解这个关系
这几个概念,上面不少答案已经写得很清楚了。这里我结合自己的理解,简单地聊一下为什么这几个概念容易混淆。如果有错误之处,恳请批评指正。
我认为同步、异步、阻塞、非阻塞,是分3个层次的:- CPU层次;
- 线程层次;
- 程序员感知层次。
这几个概念之所以容易混淆,是因为没有分清楚是在哪个层次进行讨论。
CPU层次
在CPU层次,或者说操作系统进行IO和任务调度的层次,现代操作系统通常使用异步非阻塞方式进行IO(有少部分IO可能会使用同步非阻塞轮询),即发出IO请求之后,并不等待IO操作完成,而是继续执行下面的指令(非阻塞),IO操作和CPU指令互不干扰(异步),最后通过中断的方式来通知IO操作完成结果。
在线程层次,或者说操作系统调度单元的层次,操作系统为了减轻程序员的思考负担,将底层的异步非阻塞的IO方式进行封装,把相关系统调用(如read,write等)以同步的方式展现出来。然而,同步阻塞的IO会使线程挂起,同步非阻塞的IO会消耗CPU资源在轮询上。为了解决这一问题,就有3种思路:
- 多线程(同步阻塞);
- IO多路复用(select,poll,epoll)(同步非阻塞,严格地来讲,是把阻塞点改变了位置);
- 直接暴露出异步的IO接口,如kernel-aio和IOCP(异步非阻塞)。
程序员感知层次
在Linux中,上面提到的第2种思路用得比较广泛,也是比较理想的解决方案。然而,直接使用select之类的接口,依然比较复杂,所以各种库和框架百花齐放,都试图对IO多路复用进行封装。此时,库和框架提供的API又可以选择是以同步的方式还是异步的方式来展现。如python的asyncio库中,就通过协程,提供了同步阻塞式的API;如node.js中,就通过回调函数,提供了异步非阻塞式的API。
因此,我们在讨论同步、异步、阻塞、非阻塞时,必须先明确是在哪个层次进行讨论。比如node.js,我们可以说她在程序员感知层次提供了异步非阻塞的API,也可以说在Linux下,她在线程层次以同步非阻塞的epoll来实现。