异步io实现原理(简述同步IO和异步IO的区别)

:暂无数据 2026-04-06 22:20:02 0
我们整理了关于异步io实现原理最高频的提问,发现简述同步IO和异步IO的区别位列榜首。于是,就有了这篇集中解答的精华帖。

本文目录

简述同步IO和异步IO的区别

同步是阻塞模式,异步是非阻塞模式。
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

什么是同步IO和异步IO

异步文件IO也就是重叠IO。在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。如果IO请求需要大量时间执行的话,异步文件IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉(可能会调度操作系统的零页线程)。如果IO请求操作很快,用异步IO方式反而还低效,还不如用同步IO方式。同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。异步IO在请求完成时,通过将文件句柄设为有信号状态来通知应用程序,或者应用程序通过GetOverlappedResult察看IO请求是否完成,也可以通过一个事件对象来通知应用程序。例如DeviceIoControl这个函数,他就可以通过参数指定是同步或异步,如果是同步的话,则该函数将会等待结果返回后,才执行下一条语句。如果是异步的话,DeviceIoControl调用后马上返回,如果参数正确,则回返回ERROR_IO_PENDING(忘了怎样写,不过肯定是有PENDING这个词),然后你可以通过GetOverlappedResult获取返回结果,是一个overlap结构,是在你调用DeviceIoControl的最后一个参数传进去的``简单的说``同步在编程里,一般是指某个操作执行完后,才可以执行后面的操作``拿到IO上来说``就是我要做完这个IO操作``才继续后面的操作```异步则是,我交带了某个操作给系统(可以是windows,也可以是你自己的库),我呆会过来拿,我现在要去忙别的``拿到IO上说``我交带了某个IO操作给系统。。。。。

阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一文搞定

关于IO会涉及到阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO等几个知识点。知识点虽然不难但平常经常容易搞混,特此Mark下,与君共勉。

阻塞IO情况下,当用户调用 read 后,用户线程会被阻塞,等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后 read 才会返回。可以看到是阻塞的两个部分。

非阻塞IO发出read请求后发现数据没准备好,会继续往下执行,此时应用程序会不断轮询polling内核询问数据是否准备好,当数据没有准备好时,内核立即返回EWOULDBLOCK错误。直到数据被拷贝到应用程序缓冲区,read请求才获取到结果。并且你要注意!这里最后一次 read 调用获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是 内核态的数据拷贝到用户程序的缓存区这个过程

非阻塞情况下无可用数据时,应用程序每次轮询内核看数据是否准备好了也耗费CPU,能否不让它轮询,当内核缓冲区数据准备好了,以事件通知当机制告知应用进程数据准备好了呢?应用进程在没有收到数据准备好的事件通知信号时可以忙写其他的工作。此时 IO多路复用 就派上用场了。

IO多路复用中文比较让人头大,IO多路复用的原文叫 I/O multiplexing,这里的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 发明它的目的是尽量多的提高服务器的吞吐能力。实现一个线程监控多个IO请求,哪个IO有请求就把数据从内核拷贝到进程缓冲区,拷贝期间是阻塞的!现在已经可以通过采用mmap地址映射的方法,达到内存共享效果,避免真复制,提高效率。

select、poll、epoll 都是I/O多路复用的具体的实现。

select是第一版IO复用,提出后暴漏了很多问题。

poll 修复了 select 的很多问题。

但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。

epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:

横轴 Dead connecti*** 是链接数的意思,叫这个名字只是它的测试工具叫deadcon。纵轴是每秒处理请求的数量,可看到epoll每秒处理请求的数量基本不会随着链接变多而下降的。poll 和/dev/poll 就很惨了。但 epoll 有个致命的缺点是只有 linux 支持。

比如平常Nginx为何可以支持4W的QPS是因为它会使用目标平台上面最高效的I/O多路复用模型。

然后你会发现上面的提到过的操作都不是真正的异步,因为两个阶段总要等待会儿!而真正的异步 I/O 是内核数据准备好和数据从内核态拷贝到用户态这两个过程都不用等待。

很庆幸,Linux给我们准备了 aio_read aio_write 函数实现真实的异步,当用户发起aio_read请求后就会自动返回。内核会自动将数据从内核缓冲区拷贝到用户进程空间,应用进程啥都不用管。

我强力推荐C++后端开发免费学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

同步跟异步的区别在于 数据从内核空间拷贝到用户空间是否由用户线程完成 ,这里又分为同步阻塞跟同步非阻塞两种。

我们以同步非阻塞为例,如下可看到,在将数据从内核拷贝到用户空间这一过程,是由用户线程阻塞完成的。

可发现,用户在调用之后会立即返回,由内核完成数据的拷贝工作,并通知用户线程,进行回调。

在Java中,我们使用socket进行网络通信,IO主要有三种模式,主要看 内核支持 哪些。

同步阻塞IO ,每个客户端的Socket连接请求,服务端都会对应有个处理线程与之对应,对于没有分配到处理线程的连接就会被阻塞或者拒绝。相当于是 一个连接一个线程 。

BIO特点

常量:

主类:

服务端**线程:

服务端处理线程:

客户端:

同步非阻塞IO之NIO :服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时说明读就绪,则调用该socket连接的相应读操作。如果发现某个 Socket端口上有数据可写时说明写就绪,则调用该socket连接的相应写操作。如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高,在进行IO操作请求时候再用个线程去处理,是 一个请求一个线程 。Java中使用Selector、Channel、Buffer来实现上述效果。

每个线程中包含一个 Selector 对象,它相当于一个通道管理器,可以实现在一个线程中处理多个通道的目的,减少线程的创建数量。远程连接对应一个channel,数据的读写通过buffer均在同一个 channel 中完成,并且数据的读写是非阻塞的。通道创建后需要注册在 selector 中,同时需要为该通道注册感兴趣事件(客户端连接服务端事件、服务端接收客户端连接事件、读事件、写事件), selector 线程需要采用 轮训 的方式调用 selector 的 select 函数,直到所有注册通道中有兴趣的事件发生,则返回,否则一直阻塞。而后循环处理所有就绪的感兴趣事件。以上步骤解决BIO的两个瓶颈:

下面对以下三个概念做一个简单介绍,Java NIO由以下三个核心部分组成:

channel和buffer有好几种类型。下面是Java NIO中的一些主要channel的实现:

正如你所看到的,这些通道涵盖了UDP和TCP网络IO,以及文件IO。以下是Java NIO里关键的buffer实现:

在微服务阶段,一个请求可能涉及到多个不同服务之间的跨服务器调用,如果你想实现高性能的PRC框架来进行数据传输,那就可以基于Java NIO做个支持长连接、自定义协议、高并发的框架,比如Netty。Netty本身就是一个基于NIO的网络框架, 封装了Java NIO那些复杂的底层细节,给你提供简单好用的抽象概念来编程。比如Dubbo底层就是用的Netty。

AIO是异步非阻塞IO,相比NIO更进一步,进程读取数据时只负责发送跟接收指令,数据的准备工作完全由操作系统来处理。

推荐一个零声教育C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,My**L,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 立即学习

原文:阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端

php协程是真正异步并且io复用的吗

不是。异步其实就是多线程。。启用一个线程池中的线程,去执行IO的工作,而主线程则继续向下执行。。。外在的表象,称之为异步,内在的原理,其实是多线程
由于PHP无法操作线程池中的线程,所以也就不存在真正的异步。协程是靠语法层面实现的,本质上其实是个迭代器。仅仅是"看起来像多线程"而已。本质上依然是单线程。
目前主流的WEB后端语言,可以真正操控线程的,其实只有J**A和C#。。。弱类型语言,全是靠协程来实现的“伪多线程”。在高迸发的情况下,根本不顶用。只能说“总比没有强点”
但是有些WEB框架,可以借助C语言,实现多线程IO,实际效果会比协程好非常多。。。比如Python的Tornado、Twisted、Gevent等框架,J**ASCRIPT的Node.JS框架等,都是借助C语言实现了IO部分的多线程。。虽然比不上J**A和C#的“源生多线程”,但至少比协程强多了。至于PHP,目前倒是还没听说过这种框架。

什么程序设计语言机制是处理异步 IO 最恰当的抽象

先声明一个基本假设:人的思维是线性的。程序员也是人,思维当然也是线性的。线性这里指的是事件发生顺序的前因后果关系。如果你不认同这个假设,下面讨论的几种风格的区别意义不是很大。
基于回调的异步 I/O 风格(如 Node.js 和 Python Twisted)会导致控制流倒置(inverse of control flow),使得事件发生的先后顺序不清晰明了,从而造成代码的理解和调试困难。在 Node.js 出现之前,广泛使用的 Python Twisted 库大量使用了这样的风格。一般认为这样的代码可维护性很低。基于回调的异步 I/O 的优势在于其开销小、效率高。单线程的架构也避免的多线程修改可变状态的锁的问题(当然单线程也是个限制,能够有效利用多核的方式局限于多进程)。
基于线程的同步 I/O 风格则没有这个问题:每个线程相对独立,且线程内部的控制流是线性的。理解和维护基于线程式的代码相对容易(先不谈锁的问题,不是这里讨论的重点,这里只考虑用于 I/O 的场景)。基于线程的同步 I/O 的问题是它的可扩展性很低,因为每个线程的内存开销大,在线程间切换的开销也大。对于需要处理成千上万连接的网页服务器而言,这样的开销无法接受。
很多人于是尝试保留回调和线程风格的好处(低开销、线性控制流),但同时避免他们的缺点(高开销、倒置控制流)。协程(Coroutine) 可以比较好的解决这个问题。协程允许将某个子程(Subroutine)的运行状态保留下来以便将来重入(re-entry)。也就是说,协程可以随时暂停运行,将控制返回给调用者,等条件成熟时再从暂停点继续运行。基于协程的异步 I/O 和基于回调的异步 I/O 在性能上相当,但因为协程的内部逻辑顺序是线性的,不会导致控制流倒置。主流语言中,Python 的 Generator 和 Ruby 的 Fiber 都是协程的例子。在异步 I/O 的语境下,可以将协程理解为回调模式的语法糖(当然协程还有其他的好处)。基于协程的异步 I/O 风格主要难点在于要自己写调度器(scheduler)在多个协程间切换,而且每个协程要记得频繁让出控制避免其他协程僵死。这和回调风格一样。协程现在还不那么流行,被广泛理解和接受可能还需要一些时间。
单一进程下,单纯基于回调或者协程都无法有效利用多核处理器。条件允许的情况下,一般采用运行多进程进行负载分流。基于线程的方式理论上可以利用多核,但在 Python 和 Ruby 这样有全局解释器锁(Global Interpreter Lock)的官方实现里,通常只能同时运行一个线程,多线程的优势也就局限在线性控制流,负载分流还是得通过运行多进程实现。Python 和 Ruby 的 JVM 实现由于没有全局解释器锁,不存在这个问题,多个线程可以同时运行。
线程当然不是一无是处。问题描述中引用的文献认为线程不好,是在特定的情景下(系统瓶颈主要是 I/O),且当时(2005年以前)多核处理器并非主流、内存相对有限。自从 Linux 2.6 内核开始搭载 NPTL 后,线程的开销(内存、切换)其实已经降低很多了。此外,现在的服务器多核已然是普遍现象,10GB 以上内存也很常见,上万个线程的开销已经不是问题。线程模式可以不用对程序进行特别修改就能利用越来越多的处理器核心(另外一种形式的性能『免费午餐』)。为了优化使用事件驱动的模式而必须进行的状态切换等协调操作进化到最后会成为另外一种形式的线程调度器,某些场合下还不如直接用系统的更加成熟的线程调度器。
像 Erlang 这样采用定制化调度器+轻量级线程的模式也很有意思:Erlang 的线程并非系统线程,而是 Erlang 自己管理的、类似协程的机制。和 Python、Ruby 的协程不同,Erlang 的轻量级线程切换不需要手工管理让出控制。Erlang 的调度器会在某个线程执行一定步骤(Erlang 的语境下称为『缩减』,reduction)后自动切换。这一点更加类似系统线程:内部逻辑是连续的,可以使用同步 I/O,同时又没有系统线程高开销的弊病,可谓一石三鸟。唯一问题是,这个太小众了……
另外值得一题的还有通过多个进程/事件循环提高事件驱动 I/O 性能。比如 Nginx 的工人进程(worker process)。每个工人是单独的事件循环,和其他工人独立。每个工人使用轮询机制(如 poll/epoll)时只需要处理自己手上的套接字,效率相对要高些。多核处理器上,通常为每个核心分配一个工人,这样互相之间不会为抢系统资源打架。

tornado的异步IO,长连接应该怎样理解.长连接有哪些实际的应用

结合阻塞与非阻塞访问、poll
函数可以较好地解决设备的读写,但是如果有了异步通知就更方便了。异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设
备状态,这一点非常类似于硬件上“中断”地概念,比较准确的称谓是:信号驱动(SIGIO)的异步
I/O。可以使用signal()函数来设置对应的信号的处理函数。
再说下长连接
短连接是指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送。
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个
TCP连接都需要三步握手,这需要时间,如果每个操作都是短连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,下次处理时直接发送数据
包就OK了,不用建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket
创建也是对资源的浪费。
***隐藏网址***
总之,长连接和短连接的选择要视情况而定。

IO读写原理与IO模型

为了保护操作系统安全,操作系统按照特权等级将将虚拟空间划分为两个部分,内核空间与用户空间。内核空间(Ring 0)是操作系统内核访问的区域,具有最高权限,可以直接访问所有资源。用户空间(Ring 3)是普通应用程序可访问的内存区域,只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。只能访问用户空间也就是运行在Ring 3上的程序我们称为用户程序,而运行在Ring 0上的程序我们称为内核程序。

用户程序不能访问内核空间,如果想要调用内核程序进行IO操作,必须从用户态切换为内核态,等到内核处理完之后再切换为用户态。

用户程序进行IO操作时需要进行两次状态切换:用户态-》内核态-》用户态,具体流程如下:

用户程序每进行一次系统调用都会进行两次状态切换,频繁的状态切换会影响程序运行效率,缓冲区的作用就是为了减少系统调用。缓冲区分为两种:内核缓冲区与用户缓冲区:

用户程序进行IO读写操作会发起系统调用,操作系统内核将磁盘数据读取到内核缓冲区,然后从内核缓冲区拷贝到用户缓冲区,流程如下:

举例来说,如果要读取一个文件并通过网络发送它,传统方式下每个读/写周期都需要复制两次数据和切换两次上下文,而数据的复制都需要依靠CPU。通过零复制技术完成相同的操作,上下文切换减少到两次,并且不需要CPU复制数据。

不使用零拷贝

不适用零拷贝需要经历四次数据拷贝+三次状态切换

mmap + write

mmap + write方式减少了一次CPU拷贝

sendfile

sendfile方式不涉及CPU拷贝

在讲解IO模型前,先了解下几个概念:阻塞与非阻塞、同步与异步:

阻塞IO

用户进程发起系统调用,如果数据还未准备完成,用户进程一直等待,直到数据准备完成,从内核缓冲区拷贝到用户缓冲区才返回成功提示,此次IO操作,称之为阻塞IO。缺点:如果数据没有准备好,则用户进程一直处于阻塞状态,如果想要同时处理很多请求,则需要为每个请求分配线程进行处理,系统开销较大。

非阻塞IO

用户进程发起系统调用,如果数据还未准备好,立刻返回状态标识,用户进程后续可以通过轮询的方式查看结果。缺点:虽然用户进程不阻塞了,但需要频繁的轮询获取结果,这样会导致频繁的系统调用,消耗大量的CPU资源。

IO多路复用

将IO事件注册到IO多路复用器(select/poll/epoll)上,select调用进程将一直阻塞直至注册在其上的任意一个IO时间数据准备完成时,才会返回。

信号驱动IO

用户进程发起系统调用,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。

异步IO

用户进程发起系统调用后直接返回,等到处理完成,主动通知进程结果。

对本文做个回顾:

用户进程运行在用户空间,只能访问受限资源,如果需要访问磁盘等硬件设备需要发起系统调用,从用户态切换为用户态;

为了减少频繁的系统调用,降低CPU消耗,讲解了内核缓冲区与用户缓冲区的作用;

IO读写流程,需要经历两次状态切换与两次数据拷贝;

零拷贝原理;

阻塞与非阻塞、同步与异步概念,四种同步IO模型,一种异步IO模型。

什么是“同步IO”和“异步IO”

同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。 

异步IO的概念和同步IO相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上。预先知道这些数 据的位置,所以预先发起异步IO读请求。等到真正需要用到这些数据的时候,再等待异步IO完成。使用了异步IO,在发起IO请求到实际使用数据这段时间 内,程序还可以继续做其他事情。

IO模型

这里统一使用Linux下的系统调用recv作为例子,它用于从套接字上接收一个消息,因为是一个系统调用,所以调用时会从用户进程空间切换到内核空间运行一段时间再切换回来。默认情况下recv会等到网络数据到达并且复制到用户进程空间或者发生错误时返回,而第4个参数flags可以让它马上返回。

  • 阻塞IO模型

  • 使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。A同学用杯子装水,打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。

  • 异步IO模型

  • 调用aio_read,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。E同学让舍管阿姨将杯子装满水后通知他。整个过程E同学都可以做别的事情(没有recv),这才是真正的异步IO。

操作系统IO机制

  身处不同的学习阶段时,总会对不同的知识有不同的理解,最近感慨其实计算机就是在计算和IO这两件事情上反复执行,最后完成一系列指令,虽然之前写过一篇NIO epoll的文章,但这里还是专门为操作系统的IO机制开辟一篇专栏,一起再来整理一下IO这一重要事件的机制。

  网上讲这块的内容很多,烂熟的烧开水例子相信大家都不陌生,总之,说同步和异步时,需要肯定的是这两个概念说明的是多个事物的状态,而不是单个事物的状态,多个事物串行,但每次只执行一个事物,这样的状态称为 同步 ,至于其他事物一般来说都是在等待着的状态,可以联系的生活中的场景有排队买票等;异步与同步是相反的状态, 异步 是指,多个事物可以互不干扰的同时进行,比如地铁闸机口可以允许多人在多个通道同时通过一样。

  这两个也是相反的状态,其实从字面意思已经看出十之八九, 阻塞 指的是线程在调用某方法的时候会被挂起,等到g方法返回结果时才会恢复运行; 非阻塞 指的是线程在调用某方法时立即返回一个信号,但这个信号不是方法的结果,它是一个仅仅表示调用方法的返回值,在等待方法执行的过程中,该线程还可以继续去干其他事情。
  综上所述,同步与异步关心的点在于能否同时进行,阻塞与非阻塞关心的点在于是否需要等待。

  IO就是数据的读出和写入的过程及等待数据读出和写入的过程,注意哦,这里我们还是要强调一下,IO不仅包括数据读出写入的过程,还包括等待数据读出和写入的过程,但还需牢记,一旦数据读出写入完成,剩下的操作数据的过程不属于IO的范围。
在Linux系统中,我们常见的IO机制有以下几种:
阻塞IO
非阻塞IO
多路复用IO
异步IO

  结合以上说的概念,再延伸一下, 阻塞IO 指的是应用程序发出一个阻塞系统的调用时,在等待数据到达用户空间的过程中,应用程序的执行被挂起,应用程序从操作系统的运行队列加入等待队列,等到系统调用完成后,应用程序移回到运行队列中。

  非阻塞IO,按上面说的IO的两个过程细分一下,等待数据到达用户空间(非阻塞)及在用户空间发生拷贝(阻塞)的这两个过程中,所以非阻塞IO主要是说明,用户进程不会阻塞于等待数据达到用户空间的过程中,此时它会多次发起系统调用,并马上返回错误的结果,直到数据到达用户空间会返回OK,然后继续执行拷贝,但拷贝的过程是阻塞的。所以这种方式严格一点来说,并不能将全部的过程都称为非阻塞IO,这种轮询的方式,会消耗CPU资源。

  多路复用其实是一种同步非阻塞的IO方式,epoll就是多路复用的典型,多路复用从select、poll、epoll一路进阶,详细的过程可以看专门写epoll的那篇文章,总之它的过程就是用户进程在等待数据到达用户空间的过程中,epoll会**所有的socket,用户进程也会加入epoll的等待队列,当某一个socket接收数据,epoll会把收到数据的socket加入自己的list,从而得知是具体的哪个socket收到了数据,并唤醒等待这个socket的进程,不知道在我刚刚的描述中,你是否能感受到多路复用的好处,epoll相比select的好处是无需去轮询,但在等待数据到达的这个过程,还是一个阻塞的状态。以上过程中有一点要 注意 ,对于用户进程来说,在等待数据到用户空间的过程中,其实它是阻塞于epoll的,它要等待epoll获悉具体的某一个socket收到数据后唤醒它;但对于socket来说,数据从网络传递进来需要一个过程,它等待数据的过程中,没有阻塞挂起,是非阻塞的。所以我们说的多路复用是一直同步非阻塞的方式,如果从用户进程的角度来说,好像不够严谨。
  通过以上这些过程也能发现,阻塞IO,用户程序在等待IO及拷贝的过程是线程挂起的,相当于就还是只能允许一个线程的进行,所以阻塞IO无论如何都是同步的。

  异步IO使用率不如之前的高,所以在这里简单的说一下,它不难理解,总是还是按照我们说的那两个过程去看,在等待数据到达的过程及数据拷贝的过程,用户进程都不会死等阻塞,而是去干自己的事情,当数据完成从内核空间到用户空间的拷贝后,才会通知用户进程。这样看来,异步IO都是非阻塞的。

  非阻塞和异步IO,甚至会认为非阻塞就是异步了,其实不然,一次IO操作分两个阶段,第一阶段是等待数据,第二阶段是拷贝数据(内核到用户空间),非阻塞IO操作在数据还没有到达时是立即返回的,这一点表现的跟异步一样,但是在第二个阶段,当数据准备妥当的时候,非阻塞IO是阻塞的,直到数据拷贝完成,这一点表现的与异步IO完全不一样,异步根本不关心你的数据是否到达以及你的数据是否拷贝完成,异步发起一次IO操作后就去干别的事儿了,直到内核发出一个信号通知这一事件,它才会转回来继续处理。

  IO过程主要有两个部分:等待数据(比如从网卡到达内核空间)、读写数据(从内核空间拷贝到用户空间),前三种关于阻塞IO和非阻塞IO的划分,主要是针对等待数据这个过程而言,因为从内核空间拷贝到用户空间的这个过程它们都是阻塞的。异步IO不会引起用户进程的阻塞。

linux中block IO,no-block IO,异步IO,IO多路复用笔记

        现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。 为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间 。针对linux操作系统而言, 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF) ,供内核使用,称为内核空间, 而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

        文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述 指向文件的引用的抽象化概念 。文件描述符在形式上是一个非负整数。 实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表 。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

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

1、等待数据准备 (Waiting for the data to be ready)

2、将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

正式因为这两个阶段,linux系统产生了下面 五种网络模式 的方案。

阻塞 I/O(blocking IO)

非阻塞 I/O(nonblocking IO)

I/O 多路复用( IO multiplexing)

异步 I/O(asynchronous IO)

信号驱动 I/O( signal driven IO)

注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。

阻塞 I/O(blocking IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
        当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

所以,blocking IO的特点就是在IO执行的两个阶段都被block了(内核阻塞读取数据,内核将数据复制到应用户态)。

非阻塞 I/O(nonblocking IO)

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
       当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,nonblocking IO的特点是用户进程需要 不断的主动询问 kernel数据好了没有( 内核读取数据时,用户态不需要阻塞,内核将数据复制到用户态时,需要阻塞 )。

I/O 多路复用( IO multiplexing)

         IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是 select,poll,epoll这个function会不断的轮询所负责的所有socket ,当某个socket有数据到达了,就通知用户进程。

        当用户 进程调用了select , 那么整个进程会被block ,而同时,kernel会“监视”所有 select负责的socket(一个管理多个socket连接),当任何一个socket中的数据准备好了,select就会返回 。这个时候用户进程再调用read操作, 将数据从kernel拷贝到用户进程 。

所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。 因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom) 。但是,用select的优势在于它可以同时处理多个connection。

所以,如果处理的 连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大 。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

总结:IO多路复用其实也是阻塞的,阻塞的地方在用当有socket连接有数据以后, 会阻塞知道数据从内核复制到用户态(第二步阻塞)。

异步 I/O(asynchronous IO)

inux下的asynchronous IO其实用得很少。先看一下它的流程:

        用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

总结:两个阶段都不需要用户进程干涉,内核将数据准备好以后通知用户态去读取

总结
blocking和non-blocking的区别

调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。

synchronous IO和asynchronous IO的区别

在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

- An asynchronous I/O operation does not cause the requesting process to be blocked;

两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的 blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO 。

       有人会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是, 当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。

而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

异步io实现原理不是孤立的,简述同步IO和异步IO的区别也是如此。建议你结合我们之前关于[相关主题A]和[相关主题B]的文章一起阅读,效果更佳。
本文编辑:admin

更多文章:


免费网上购物系统源码(免费开源网上商城系统有哪些)

免费网上购物系统源码(免费开源网上商城系统有哪些)

各位朋友,关于免费网上购物系统源码的讨论一直很多,今天咱们不聊复杂的,就聚焦于免费开源网上商城系统有哪些,用最直白的方式把它讲清楚。

2026年4月7日 00:00

百度站长工具平台官网(网站站长查询)

百度站长工具平台官网(网站站长查询)

有没有觉得百度站长工具平台官网听起来很高深?别怕,今天我们就把它和网站站长查询一起,拆解成易懂的小知识点。

2026年4月6日 23:40

李哥瑞兹皮肤(为什么 Faker 从来不用皮肤)

李哥瑞兹皮肤(为什么 Faker 从来不用皮肤)

有没有这种经历:明明想搞懂李哥瑞兹皮肤,却被为什么 Faker 从来不用皮肤卡住了脖子?今天这篇文章,就是专治这种“卡脖子”问题的。

2026年4月6日 23:20

oppo手机字体大小怎么调(oppo手机桌面字体大小怎么设置)

oppo手机字体大小怎么调(oppo手机桌面字体大小怎么设置)

您是否正在为搞不清oppo手机字体大小怎么调和oppo手机桌面字体大小怎么设置的关系而烦恼?恭喜,这篇干货就是您的“及时雨”。

2026年4月6日 23:00

excel表格使用教程视频(excel怎么做表格视频教程的相关视频)

excel表格使用教程视频(excel怎么做表格视频教程的相关视频)

有没有觉得excel表格使用教程视频听起来很高深?别怕,今天我们就把它和excel怎么做表格视频教程的相关视频一起,拆解成易懂的小知识点。

2026年4月6日 22:40

异步io实现原理(简述同步IO和异步IO的区别)

异步io实现原理(简述同步IO和异步IO的区别)

我们整理了关于异步io实现原理最高频的提问,发现简述同步IO和异步IO的区别位列榜首。于是,就有了这篇集中解答的精华帖。

2026年4月6日 22:20

phpstorm免费30天(phpstorm eap 是什么版本)

phpstorm免费30天(phpstorm eap 是什么版本)

最新数据显示,关注phpstorm免费30天的人中,超过70%都对phpstorm eap 是什么版本抱有浓厚兴趣。本文将满足这一核心需求。

2026年4月6日 22:00

asp文件打开怎么是乱码(ASP打代码的时候浏览总是乱码怎么办)

asp文件打开怎么是乱码(ASP打代码的时候浏览总是乱码怎么办)

当大家谈论asp文件打开怎么是乱码时,总免不了提及ASP打代码的时候浏览总是乱码怎么办。它们之间究竟有何玄机?读完本文你便了然于胸。

2026年4月6日 21:40

接口中的抽象方法(在抽象类中定义抽象方法需要用abstract声明,但是在接口中定义的抽象方法是不是可以不用abstract声明)

接口中的抽象方法(在抽象类中定义抽象方法需要用abstract声明,但是在接口中定义的抽象方法是不是可以不用abstract声明)

关于接口中的抽象方法,您需要知道的几个关键点,尤其是在抽象类中定义抽象方法需要用abstract声明,但是在接口中定义的抽象方法是不是可以不用abstract声明的深入解析,我们都将在这篇文章中涵盖。

2026年4月6日 21:20

哈夫曼树构造0和1编码有规则吗(哈夫曼树编码一定是左边为0,右边为1吗)

哈夫曼树构造0和1编码有规则吗(哈夫曼树编码一定是左边为0,右边为1吗)

想高效掌握哈夫曼树构造0和1编码有规则吗的核心吗?本文将为你聚焦哈夫曼树编码一定是左边为0,右边为1吗这一关键环节,帮你节省大量摸索时间。

2026年4月6日 21:00

最近更新

phpstorm免费30天(phpstorm eap 是什么版本)
2026-04-06 22:00:02 浏览:0
热门文章

php中session的用法(PHP session干嘛用的举个简单易懂的例子)
2026-04-03 18:00:02 浏览:0
powershell语法(powershell语法之:Set-Variable,谁能帮我解释下,3Q)
2026-04-03 10:00:01 浏览:0
标签列表