网络I/O中的同步、异步、阻塞和非阻塞概念
在学习网络编程过程中,经常会把这几个概念搞混淆。
同步I/O与异步I/O区别
我们先来看一下操作I/O时涉及的对象和步骤(这里我们以read为例):
这里会涉及到两个系统对象,一个是调用这个I/O的应用进程(或线程),另一个就是系统内核。当一个read操作发生时,它会经历两个阶段:1)等待数据就绪 (可读);2)将数据从内核拷贝到应用进程中 。这两个阶段很重要,因为各种I/O模型的区别就是在这两个阶段上各有不同的情况。
下边我们来看一下同步I/O及异步I/O。
根据UNP一书给出的定义(POSIX):
- 同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成。UNP第6章中提到的I/O模型——阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型和信号驱动式I/O模型都是同步I/O模型,因为其中真正的I/O操作将阻塞进程。
- 异步I/O操作(asynchronous I/O operation)不导致请求进程阻塞。
可以看出,同步I/O和异步I/O的核心区别在于真正的I/O操作会不会阻塞进程。具体来说,同步I/O需要进程真正地去操作I/O,而异步I/O则由内核在I/O操作完成后再通知应用进程结果。
我们来看一下UNP一书给出的对5种I/O模型的比较表就可能更清楚了:

由上图,我们可以知道,除了异步I/O模型,其他模型都会实际阻塞于真正的I/O操作(如recvfrom,这里真正的I/O操作不包括图中的‘检查’)。上图也说明了非阻塞式I/O虽然在检查阶段不会阻塞,但在文件描述符就绪(如可读)的时候是会阻塞的,这是它区别于异步I/O很重要的一点。
对于同步I/O操作,一个典型的例子就是libevent网络库。而对于异步I/O操作,比较有名的例子就是Boost库的ASIO库。ACE库则包括了同步I/O及异步I/O两种方式。
同步I/O
上边我们知道同步I/O操作包括了阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型和信号驱动式I/O。
阻塞式I/O模型
在Linux中,默认情况下所有的套接字都是阻塞的。以数据报套接字为例,一个典型的读操作流程大概是这样:

进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发生错误才返回。最常见的错误是系统调用被信号中断。我们说进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。
非阻塞式I/O模型
进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。如下例:

前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,于是recvfrom成功返回。我们接着处理数据。
当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询(polling)。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间。
I/O复用模型
关于I/O复用,知乎上有比较透彻的一个解释:“关于I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。操作系统的这个功能通过select/poll/epoll/kqueue之类的系统调用函数来使用,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。”
有了I/O复用(I/O multiplexing),我们就可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。下图概括展示了I/O复用模型:

我们阻塞于select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用进程缓冲区。
比较图6-3和图6-1,I/O复用并不显得有什么优势,事实上由于使用select需要两个而不是单个系统调用,I/O复用还稍有劣势。不过select的优势在于可以等待多个描述符就绪(与此相对应的方法是多线程+阻塞式I/O,即由每一个线程来调用阻塞式I/O系统调用)。
信号驱动式I/O模型
我们也可以用信号,让内核在描述符就绪时发送SIGIO信号给我们。这种模型称为信号驱动式I/O(signal-driven I/O)。下图是其概要展示:

我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作, 也就是说它没有被阻塞。当数据报准备好读写时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
异步I/O
异步I/O(asynchronous I/O)由POSIX规范定义。一般来说,用于实现异步I/O的函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前面介绍的信号驱动模型的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。下图给出了一个例子:

我们调用aio_read函数(POSIX异步I/O函数以aio_或lio_开头),给内核传递描述符、缓冲区指针、缓冲区大小(与read相同的三个参数)和文件偏移(与lseek类似),并告诉内核当整个操作完成时如何通知我们。该系统调用立即返回,而且在等待I/O完成期间,我们的进程不被阻塞。本例子中我们假设要求内核在操作完成时产生某个信号。该信号直到数据已复制到应用进程缓冲区时才发生,这一点不同于信号驱动式I/O模型。
参考资料
UNP
网络I/O中的同步、异步、阻塞和非阻塞概念的更多相关文章
- socket网络编程中的同步,异步,阻塞式,非阻塞式,有何联系与区别?
一.举个打电话的例子: 阻塞 block 是指,你拨通某人的电话,但是此人不在,于是你拿着电话等他回来,其间不能再用电话.同步大概和阻塞差不多. 非阻塞 nonblock 是指,你拨通 ...
- 网络IO之阻塞、非阻塞、同步、异步总结
网络IO之阻塞.非阻塞.同步.异步总结 1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一 ...
- python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)
python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程 并行与并发 同步与异步 阻塞与非阻塞 CPU密集型与IO密集型 线程与进程 进 ...
- 网络IO之阻塞、非阻塞、同步、异步总结【转】
1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一遍时候,似懂非懂,没有深入理解.网上有详 ...
- IO中同步、异步与阻塞、非阻塞的区别
一.同步与异步同步/异步, 它们是消息的通知机制 1. 概念解释A. 同步所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回. 按照这个定义,其实绝大多数函数都是同步调用(例如si ...
- IO中同步、异步与阻塞、非阻塞的区别(转)
转自:http://blog.chinaunix.net/uid-26000296-id-3754118.html 一.同步与异步同步/异步, 它们是消息的通知机制 1. 概念解释A. 同步所谓同步, ...
- node.js中对同步,异步,阻塞与非阻塞的理解
我们都知道javascript是单线程的,node.js是一个基于Chrome V8 引擎的 javascript 运行时环境,注意 node.js 不是一门语言,别搞错了. javascript为什 ...
- Linux中同步与异步、阻塞与非阻塞概念以及五种IO模型
1.概念剖析 相信很多从事linux后台开发工作的都接触过同步&异步.阻塞&非阻塞这样的概念,也相信都曾经产生过误解,比如认为同步就是阻塞.异步就是非阻塞,下面我们先剖析下这几个概念分 ...
- 谈IO中的阻塞和非阻塞,同步和异步及三种IO模型
什么是同步和异步? 烧水,我们都是通过热水壶来烧水的.在很久之前,科技还没有这么发达的时候,如果我们要烧水,需要把水壶放到火炉上,我们通过观察水壶内的水的沸腾程度来判断水有没有烧开.随着科技的发展,现 ...
随机推荐
- Swift:一个简单的货币转换器App在iOS10中的分析和完善
这本不算是一个完整的货币转换App,只不过是一个小巧的学习性质的程序.该App覆盖了如下几个知识点: 多国语言的支持 通过网络Api接口读取数据 最后我们来修复一个原来代码中的一个小错误作为完美的收尾 ...
- 微信小程序基础之常用控件text、icon、progress、button、navigator
今天展示一下基础控件的学习开发,希望对大家有所帮助,转载请说明~ 首先延续之前的首页界面展示,几个跳转navigator的使用,然后是各功能模块的功能使用 一.text展示 使用按钮,进行文字的添加与 ...
- iOS学习笔记--数据存储
iOS应用数据存储的常用方式 XML属性列表(plist)归档 Preference(偏好设置) NSKeyedArchiver归档(NSCoding) SQLite3 Core Data 1. XM ...
- Python 3 re模块3个括号相关的语法
(?aiLmsux) (One or more letters from the set 'a', 'i', 'L', 'm', 's', 'u', 'x'.) The group matches t ...
- Android应用UI设计流程
Android应用UI设计流程 设计原理 1.在移动设计中,使用环境是最关键的因素.原型设计方法必须考虑尺寸因素 2.用户测试必须涵盖运动.声音和多点触控等方面: 进行移动设计和测试时,请将你知道的有 ...
- SQL Server专家的10个秘诀(翻译加注解)
当你点开这篇文章的时候,如果觉得没有读下去的必要,也希望你能拉到最后看看那几行字! 原文出处:https://technet.microsoft.com/en-us/magazine/gg299551 ...
- Java基本语法-----java函数
函数的概述 发现不断进行加法运算,为了提高代码的复用性,就把该功能独立封装成一段独立的小程序,当下次需要执行加法运算的时候,就可以直接调用这个段小程序即可,那么这种封装形形式的具体表现形式则称作函数. ...
- JVM学习之-栈
JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题,即数据怎么放.放在哪儿,另外JVM堆中存的是对象.JVM栈中存的是基本数据类型和JVM堆中对象的引用. ...
- API创建员工支付方式
DECLARE ln_method_id PAY_PERSONAL_PAYMENT_METHODS_F.PERSONAL_PAYMENT_METHOD_ID%TYPE; ln_ext_acc_id P ...
- python的u'字符串"(字符编码):字符串前有u,表示字符串以unicode格式存储
举个例子 >>> s = u'\u6ce8\u91ca' >>> s u'\u6ce8\u91ca' >>> print s 注释 >> ...