关于网络IO中的同步、异步、阻塞、非阻塞

简介: 在高并发编程当中,我们经常会遇到一些异步、非阻塞等一些概念,一些常用的技术比如异步的httpclient、netty nio、nginx、node.js等,它们的原理大都跟异步、非阻塞有关。特别是在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。其nginx和node.js处理并

在高并发编程当中,我们经常会遇到一些异步、非阻塞等一些概念,一些常用的技术比如异步的httpclient、netty nio、nginx、node.js等,它们的原理大都跟异步、非阻塞有关。特别是在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。其nginx和node.js处理并发都是采用的事件驱动异步非阻塞模式。其中nginx中处理并发用的是epoll,poll,queue等方式,node.js使用的是libev,它们对大规模的HTTP请求处理的都很好。

那么到底什么是异步、非阻塞,它们的原理是什么,它们之间又有什么区别呢?其实在很多情况下,异步与非阻塞(同步与阻塞)表示的是同一个意思,但是在特定的上下文环境中,它们含义又十分不同。再具体讲它们的区别之前,先介绍一下上下文背景。

一、上下文背景

我们所遇到的这些场景大部分都是当用户进程(或线程)在进行网络IO时即进行Socket读写时遇到的,所以本文讨论的上下文背景是基于Linux环境下的network IO。先介绍一下其中我们最常见的五种IO:

1.  blocking IO
2.  nonblocking IO
3.  IO multiplexing
4.  signal driven IO
5.  asynchronous IO

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

再说一下IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的进程(或线程),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:

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

记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。

二、各种IO介绍

2.1 blocking IO

在linux中,默认情况下所有的socket都是blocking,也就是说我们的一个进程在进行IO操作时如果没有数据达到,这个进程是被阻塞的。一个典型的读操作流程大概是这样:
这里写图片描述

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。所以,blocking IO的特点就是在IO执行的wait和copy两个阶段都被block了

在这种block IO的情况下,如果请求的连接比较多,但其中大部分都是阻塞的。因为cpu的核数是有限的,所以一般的解决方案就是每个cpu启用多个线程来处理多个连接。这种解决方案有很大的缺陷:

1. 线程是有内存开销的,1个线程可能需要512K(或2M)存放栈,那么1000个线程就要512M(或2G)内存
2. 线程的切换开销和很大,因为线程切换时需要保持当前线程上下文信息,当大量时间花在上下文切换的时候,分配给真正的操作的CPU就要少很多
3. 一个cpu所支持的线程数量时有限的(因为上面两个原因),一般来说线程的数量级在几百个左右就已经很大了

为了解决block IO存在的问题,就引入了no-blocking IO概念。

2.2 non-blocking IO

no-blocking IO很简单,通过将socket设为非阻塞模式,这时,当你调用read时,如果有数据就绪,就返回数据,如果没有数据就绪,就立刻返回一个错误,如EWOULDBLOCK。这样是不会阻塞线程了,但是你还是要不断的轮询来读取或写入。当对一个non-blocking socket执行读操作时,流程是这个样子:
这里写图片描述
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

从上面介绍可以看到,blocking IO的特点就是在IO执行的wait阶段是非阻塞的,但是copy阶段还是阻塞的

但是no-blocking IO也存在很大的缺陷,就是IO线程还是要不断的轮询socket来读取或写入, 于是,我们又引入了IO多路复用。

2.3 IO multiplexing(IO多路复用)

IO multiplexing即IO多路复用,有些地方也称这种IO方式为event driven IO(事件驱动IO)。它的基本原理就是用通过操作系统提供的select/epoll等这些函数不断的轮询所负责的所有socket,而不是让用户进程自己去轮询,注意这个socket必须先设成异步的socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
这里写图片描述
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

由上面的图示可知,采用多路模型会多一次系统调用select,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

那么IO多路复用的优势在哪里呢,其实就是在”多路复用”这个词上。上面也讲到了多路复用是指使用一个线程来检查多个Socket(也成文件描述符 )的就绪状态,比如调用select和epoll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。所以,在高并发的场景中,比如要处理10000个连接,只需要1个线程监控就绪状态,对就绪的每个连接开一个线程处理或者直接丢到线程池处理,当然也可以用当前线程处理,那么这个IO线程可以同时管理多个连接,也就是多路复用了。

2.4 Asynchronous I/O

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

三、各种IO之间的区别

到目前为止,已经将四个IO Model都介绍完了。现在回过头来回答最初的那几个问题:blocking和non-blocking的区别在哪,synchronous IO和asynchronous IO的区别在哪。

blocking vs non-blocking,这个问题很简单,前面的介绍中其实已经很明确的说明了这两者的区别:

1. blocking IO 会在wait和copy阶段都会阻塞进程
2. non-blocking IO 在wait阶段会立即返回不会阻塞进程,而在copy阶段仍会阻塞进程copy数据

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

1. synchronous I/O:IO操作过程中进程会被阻塞,直到IO操作完成
2. asynchronous I/O:IO操作过程中进程不会被阻塞,操作系统帮你完成IO操作之后直接返回给你

按照这个定义,在网络IO层面,同步异步相对于阻塞非阻塞是一个更加宏观的概念,之前所述的阻塞IO,非阻塞IO,IO多路复用都属于同步IO,因为它们在内核copy数据阶段都会阻塞进程。而异步IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到操作系统内核发送一个信号,告诉进程说操作系统IO已经完成,在这整个过程中,进程完全没有被阻塞。

各个IO Model的比较如图所示:
这里写图片描述

经过上面的介绍,会发现非阻塞IO和异步IO的区别还是很明显的。在非阻塞 IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而异步 IO则完全不同,它就像是用户进程将整个IO操作交给了操作系统(内核)完成,然后操作系统做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

参考文档:http://blog.csdn.net/historyasamirror/article/details/5778378

目录
相关文章
|
7天前
|
开发框架 并行计算 算法
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
20 4
|
6天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
45 5
|
13天前
|
机器学习/深度学习 安全 网络安全
云端盾牌:云计算时代的网络安全守护在这个数字脉搏加速跳动的时代,云计算以其高效、灵活的特性,成为推动企业数字化转型的强劲引擎。然而,正如每枚硬币都有两面,云计算的广泛应用也同步放大了网络安全的风险敞口。本文旨在探讨云计算服务中网络安全的关键作用,以及如何构建一道坚不可摧的信息防线,确保数据的安全与隐私。
云计算作为信息技术领域的革新力量,正深刻改变着企业的运营模式和人们的生活。但在享受其带来的便利与效率的同时,云服务的安全问题不容忽视。从数据泄露到服务中断,每一个安全事件都可能给企业和个人带来难以估量的损失。因此,本文聚焦于云计算环境下的网络安全挑战,分析其根源,并提出有效的防护策略,旨在为云服务的安全使用提供指导和参考。
|
9天前
|
存储 机器人 Linux
Netty(二)-服务端网络编程常见网络IO模型讲解
Netty(二)-服务端网络编程常见网络IO模型讲解
|
9天前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
在数据驱动时代,高效处理大规模数据和高并发请求至关重要。Python凭借其优雅的语法和强大的库支持,成为开发者首选。本文将介绍Python中的并发与异步编程,涵盖并发与异步的基本概念、IO密集型任务的并发策略、CPU密集型任务的并发策略以及异步IO的应用。通过具体示例,展示如何使用`concurrent.futures`、`asyncio`和`multiprocessing`等库提升程序性能,帮助开发者构建高效、可扩展的应用程序。
22 0
|
2月前
|
存储 Java 数据库连接
BIO阻塞IO流与数据存储大揭秘:性能与资源消耗,一文让你彻底解锁!
【8月更文挑战第25天】本文探讨了Java中BIO阻塞IO流与数据存储的概念及其实现。BIO作为一种传统IO模型,在处理每个客户端请求时需创建新线程并等待响应,这在并发量大时会导致性能下降和高资源消耗。示例代码展示了如何利用`ServerSocket`实现基于BIO的简单服务器。此外,文章还介绍了数据存储的基本方法,例如通过`BufferedWriter`向文件写入数据。两者对比显示,BIO适合连接数稳定的场景,而数据存储则适用于需要持久化保存信息的情况。通过这些分析和实例,希望能帮助读者更好地掌握这两种技术的应用场景及其优缺点。
32 0
|
2月前
|
缓存
PUN☀️八、拓展网络同步:RPCs 和 Properties
PUN☀️八、拓展网络同步:RPCs 和 Properties
|
2月前
|
消息中间件 网络协议 Java
你不得不了解的网络IO模型知识
该文章主要讲述了网络I/O模型的相关知识,包括不同的I/O模型以及它们的特点和应用场景。
你不得不了解的网络IO模型知识
|
2月前
|
UED 存储 数据管理
深度解析 Uno Platform 离线状态处理技巧:从网络检测到本地存储同步,全方位提升跨平台应用在无网环境下的用户体验与数据管理策略
【8月更文挑战第31天】处理离线状态下的用户体验是现代应用开发的关键。本文通过在线笔记应用案例,介绍如何使用 Uno Platform 优雅地应对离线状态。首先,利用 `NetworkInformation` 类检测网络状态;其次,使用 SQLite 实现离线存储;然后,在网络恢复时同步数据;最后,通过 UI 反馈提升用户体验。
47 0
|
2月前
|
Ubuntu Linux
内核实验(九):添加IO驱动的阻塞读写功能
本文通过修改内核模块代码,介绍了如何在Linux内核中为IO驱动添加阻塞读写功能,使用等待队列和条件唤醒机制来实现读写操作的阻塞和非阻塞模式,并在Qemu虚拟机上进行了编译、部署和测试。
11 0
下一篇
无影云桌面