read/write 阻塞/非阻塞
read
函数从打开的设备或文件中读取数据。
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count); 返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0
参数count
是请求读取的字节数,读上来的数据保存在缓冲区buf
中,同时文件的当前读写位置向后移。注意这个读写位置和使用C标准I/O库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用fgetc
读一个字节,fgetc
有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE
结构体中记录的读写位置是1。注意返回值类型是ssize_t
,表示有符号的size_t
,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。read
函数返回时,返回值说明了buf
中前多少个字节是刚读上来的。有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count
,例如:
读常规文件时,在读到
count
个字节之前已到达文件末尾。例如,距文件末尾还有30个字节而请求读100个字节,则read
返回30,下次read
将返回0。从终端设备读,通常以行为单位,读到换行符就返回了。
从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数,后面socket编程部分会详细讲解。
write
函数向打开的设备或文件中写数据。
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count); 返回值:成功返回写入的字节数,出错返回-1并设置errno
写常规文件时,write
的返回值通常等于请求写的字节数count
,而向终端设备或网络写则不一定。
读常规文件是不会阻塞的,不管读多少字节,read
一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read
读终端设备就会阻塞,如果网络上没有接收到数据包,调用read
从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep
指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:
正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(
eip
)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
下面这个小程序从终端读数据再写回终端。
例 28.2. 阻塞读终端
#include <unistd.h> #include <stdlib.h>int main(void) {char buf[10];int n;n = read(STDIN_FILENO, buf, 10);if (n < 0) {perror("read STDIN_FILENO");exit(1);}write(STDOUT_FILENO, buf, n);return 0; }
执行结果如下:
$ ./a.out hello(回车) hello $ ./a.out hello world(回车) hello worl$ d bash: d: command not found
第一次执行a.out
的结果很正常,而第二次执行的过程有点特殊,现在分析一下:
Shell进程创建
a.out
进程,a.out
进程开始执行,而Shell进程睡眠等待a.out
进程退出。a.out
调用read
时睡眠等待,直到终端设备输入了换行符才从read
返回,read
只读走10个字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。a.out
进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符d和换行符,把它当成一条命令解释执行,结果发现执行不了,没有d这个命令。
如果在open
一个设备时指定了O_NONBLOCK
标志,read
/write
就不会阻塞。以read
为例,如果设备暂时没有数据可读就返回-1,同时置errno
为EWOULDBLOCK
(或者EAGAIN
,这两个宏定义的值相同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:
while(1) {非阻塞read(设备1);if(设备1有数据到达)处理数据;非阻塞read(设备2);if(设备2有数据到达)处理数据;... }
如果read(设备1)
是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read
调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。
非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。在使用非阻塞I/O时,通常不会在一个while
循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。
while(1) {非阻塞read(设备1);if(设备1有数据到达)处理数据;非阻塞read(设备2);if(设备2有数据到达)处理数据;...sleep(n); }
这样做的问题是,设备1有数据到达时可能不能及时处理,最长需延迟n秒才能处理,而且反复查询还是做了很多无用功。以后要学习的select(2)
函数可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,从而圆满地解决了这个问题。
以下是一个非阻塞I/O的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端来做这个实验。程序开始执行时在0、1、2文件描述符上自动打开的文件就是终端,但是没有O_NONBLOCK
标志。所以就像例 28.2 “阻塞读终端”一样,读标准输入是阻塞的。我们可以重新打开一遍设备文件/dev/tty
(表示当前终端),在打开时指定O_NONBLOCK
标志。
例 28.3. 非阻塞读终端
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdlib.h>#define MSG_TRY "try again\n"int main(void) {char buf[10];int fd, n;fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);if(fd<0) {perror("open /dev/tty");exit(1);} tryagain:n = read(fd, buf, 10);if (n < 0) {if (errno == EAGAIN) {sleep(1);write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));goto tryagain;} perror("read /dev/tty");exit(1);}write(STDOUT_FILENO, buf, n);close(fd);return 0; }
以下是用非阻塞I/O实现等待超时的例子。既保证了超时退出的逻辑又保证了有数据到达时处理延迟较小。
例 28.4. 非阻塞读终端和等待超时
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdlib.h>#define MSG_TRY "try again\n" #define MSG_TIMEOUT "timeout\n"int main(void) {char buf[10];int fd, n, i;fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);if(fd<0) {perror("open /dev/tty");exit(1);}for(i=0; i<5; i++) {n = read(fd, buf, 10);if(n>=0)break;if(errno!=EAGAIN) {perror("read /dev/tty");exit(1);}sleep(1);write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));}if(i==5)write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));elsewrite(STDOUT_FILENO, buf, n);close(fd);return 0; }
read/write 阻塞/非阻塞相关推荐
- 异步通知是什么意思_一次相亲经历,我彻底搞懂了阻塞非阻塞、同步异步
看到标题,可能你会想,相亲跟阻塞/非阻塞,同步/异步有啥关系,这个逗b不知道在想什么东西.不要急,且听我慢慢道来 年纪大了,一回家七大姑八大姨就各种催婚,都说要给我介绍女朋友.这不,刚刚门口,我的大姨 ...
- 15分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行,太实用了!
作者:Martin cnblogs.com/mhq-martin/p/9035640.html 基本概念 1 进程和线程 进程(Process): 是Windows系统中的一个基本概念,它包含着一个运 ...
- 迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)...
常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数据,然后使用适合的视图展示详情数据. 如果网速很慢,代码发起一个HTTP请求后,就卡住不动了,直到十几秒后才拿到HTT ...
- 同步 异步 阻塞 非阻塞概念区分
老张爱喝茶,废话不说,煮开水. 提前剧透一下:同步和非同步主要用来形容被调用线程,阻塞非阻塞用来形容主线程的. 出场人物:老张(主线程),水壶(被调用线程)两把(普通水壶,简称水壶:会响的水壶,简称响 ...
- 阻塞/非阻塞与同步/异步的区别
阻塞,非阻塞 同步,异步 阻塞与非阻塞最大的区别是调用方一直等待还是先去处理别的事情. 同步与异步最大的区别就是被调用方返回结果之前的这段时间内,调用方是否一直等待. 那么阻塞和同步,非阻塞和异步是一 ...
- 【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)...
网上有很多讲同步/异步/阻塞/非阻塞/BIO/NIO/AIO的文章,但是都没有达到我的心里预期,于是自己写一篇出来. 常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数 ...
- Socket 同步/异步 与阻塞/非阻塞区别
2019独角兽企业重金招聘Python工程师标准>>> 在网上看了很多答案,也没找到合适的,也许本文也不是合适答案:) 同步和异步关注的是消息通信机制,而阻塞非阻塞关注的是程序在等待 ...
- 异步同步、阻塞非阻塞、异步回调、线程队列和协程
今天学习了异步同步.阻塞非阻塞.异步回调.线程队列和协程 一.异步同步和阻塞非阻塞 线程的三种状态: 1.就绪 2.运行 3.阻塞 阻塞:遇到了IO操作 代码卡住 无法执行下一行 CPU会切换到 ...
- NIO详解(二): BIO 浅谈 同步 异步与阻塞 非阻塞
在我们了解Java NIO/BIO的网络通信之前,我们先了解一下常用的阻塞/非阻塞模型以及同步/异步的概念 一.阻塞和非阻塞 从简单的开始,我们以经典的读取文件的模型举例.(对操作系统而言,所有的输入 ...
- 异步/同步、阻塞/非阻塞的理解
异步/同步.阻塞/非阻塞的理解 [同步和异步] 通俗的讲: 同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式. 异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个 ...
最新文章
- Oracle查询优化-04插入、更新与删除数据
- 好文推荐 | MySQL binlog应用场景与原理深度剖析
- Firefox about
- linux类似360软件,linux下有什么类似鲁大师查看电脑配置的软
- Linux 驱动面试题总结
- hdoj-3342-Legal or Not(拓扑排序)
- axios拦截器_请求拦截器_响应拦截器---axios工作笔记010
- 数据库建模——用PowerDesigner对现有的数据库进行逆向工程
- LNMP详解(二)——Nginx源码安装与启动
- 刷题记录 CF每日一题打卡 2020.5月26-6月2
- 1016day3:city查询系统json模块、餐厅点菜系统、点餐系统(class类)
- 你知道直方图都能干啥?
- Linux(Ubuntu)触摸屏校准
- 《RSSHub Radar》可以帮助你快速发现和订阅当前网站RSS的浏览器扩展
- 关于云计算--openstack
- 用python实现二分法求平方根_二分法求平方根(Python实现)
- vue基础--vue的生命周期
- 一步一步构建手机WebApp开发——环境搭建
- STM32单片机(1) 总记 学习资料+参考手册+LED灯
- Slow down:快时尚自救计划