阻塞与非阻塞||同步与异步
转自:https://www.zhihu.com/question/19732473
https://blog.csdn.net/mianhuantang848989/article/details/83961337
1.阻塞与非阻塞
https://blog.csdn.net/lengxiao1993/article/details/78154467
1.1 进程间通信
分别对于进程、对于IO系统调用来说:
对进程阻塞挺好理解,就是发出一个调用,就由running转换为waiting状态,直到结果返回才执行下一行代码。
进程间的通信时通过 send() 和 receive() 两种基本操作完成的。具体如何实现这两种基础操作,存在着不同的设计。
消息的传递有可能是**阻塞的**或**非阻塞的** -- 也被称为**同步**或**异步**的
:
- 阻塞式发送(blocking send). 发送方进程会被一直阻塞, 直到消息被接受方进程收到。
- 非阻塞式发送(nonblocking send)。 发送方进程调用 send() 后, 立即就可以其他操作。
- 阻塞式接收(blocking receive) 接收方调用 receive() 后一直阻塞, 直到消息到达可用。
- 非阻塞式接受(nonblocking receive) 接收方调用 receive() 函数后, 要么得到一个有效的结果, 要么得到一个空值, 即不会被阻塞。
在进程通信层面, 阻塞/非阻塞, 同步/异步基本是同义词, 但是需要注意区分讨论的对象是发送方还是接收方。
- 发送方阻塞/非阻塞(同步/异步)和接收方的阻塞/非阻塞(同步/异步) 是互不影响的。
//但是非阻塞的意思是,调用了,立马返回了一个结果,然后继续执行?我不太懂,直接返回返回什么?如果后续要用到读取到的数据那岂不是会出错误?非阻塞的话是当前进程/线程继续执行,还是说切换了呢?
知乎这个问题:https://www.zhihu.com/question/446646214,
回答中举例
有很多情况下,一部分代码是做费时的工作的,就可以先去执行后面的部分。
这个回答很清晰,两种方法,一种while轮询,一种就是先注册回调函数。后面3.3.1给出的例子就是通过非阻塞并且轮询的方式,有数据就读取,并没有阻塞于read()一次性读取完所有的数据,有的话就读,没有的话就处理面的,轮询得到结果,如果没有就执行后面的,一直轮询知道结束。
1.2 IO系统调用
进程发出系统调用后:
- 可以阻塞式地等待IO 设备的返回结果,阻塞式的调用,使得应用级代码的编写更容易(代码的执!行顺序和编写顺序是一致的)。
- 也会提供非阻塞I/O 系统调用接口,一个非阻塞调用不会挂起调用程序, 而是会立即返回一个值, 表示有多少bytes 的数据被成功读取(或写入)。
和非阻塞IO系统调用同等的是异步IO系统调用,但两者并不相等,返回结果不一样。与非阻塞 I/O 系统调用类似,asychronous system call 也是会立即返回, 不会等待 I/O 操作的完成, 应用程序可以继续执行其他的操作, 等到 I/O 操作完成了以后,操作系统会通知调用进程(设置一个用户空间特殊的变量值 或者 触发一个 signal 或者 产生一个软中断 或者 调用应用程序的回调函数)。
非阻塞IO系统调用和异步IO系统调用的区别:
- 一个非阻塞I/O 系统调用 read() 操作立即返回的是任何可以立即拿到的数据, 可以是完整的结果, 也可以是不完整的结果, 还可以是一个空值。
- 而异步I/O系统调用 read()结果必须是完整的, 但是这个操作完成的通知可以延迟到将来的一个时间点。
2.同步与异步
在进程同步的上下文中,表示send与receive方是否阻塞;
在IO系统调用中,即进程在发出系统调用后所处的状态,同步的话会阻塞(IO系统调用一般不说同步吧,同步意思就是阻塞?得要等到读取结果之后才进行下一步),异步的话就会继续执行下面的代码。
3.IO函数
https://blog.csdn.net/kwinway/article/details/80708878
3.1 send:
阻塞与非阻塞send返回值没有区分,都是 <0:出错,=0:连接关闭,>0发送数据大小,
特别:非阻塞模式下返回值 <0时并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的, 继续发送。
只是阻塞模式下send会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着 send,因此需要循环发送。
3.1.1 errno的解释:
https://blog.csdn.net/wm_1991/article/details/51858997
- EINTR:指操作被中断唤醒,需要重新读/写
- EWOULDBLOCK:用于非阻塞模式,不需要重新读或者写
- EAGAIN:比较常见的一个错误(比如用在非阻塞操作中),提示再试一次。比如连续做read操作而没有数据可读,此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。
3.2 recv:
返回同上。
只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要 循环读取。
3.3 write/read
只是阻塞模式下write会阻塞着发送数据;非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着 write,因此需要循环发送。
只是阻塞模式下read会阻塞着接收数据;非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要 循环读取。
3.3.1 非阻塞读写例子
https://blog.csdn.net/wm_1991/article/details/51858997
读数据:
ssize_t readn ( int fd, void *vptr, size_t n ) { size_t nleft; ssize_t nread; char *ptr; ptr=vptr; nleft=n; while ( nleft>0 )//循环读取 { if ( ( nread = read ( fd,ptr,nleft ) ) < 0 )//读取到fd中 { if ( errno == EINTR )//未读取到,读中断 nread = 0; else return ( -1 ); } nleft-=nread; ptr+=nread; } return ( n-nleft ); }
写数据:
ssize_t writen ( int fd, const void *ptr, size_t n ) { size_t nleft; ssize_t nwritten; const char *ptr; ptr=vptr; nleft=n; while ( nleft>0 )//循环写数据 { if( ( nwritten=write( fd, ptr, nleft ) )<=0 ) { if( nwritten<0 && errno == EINTR )//写中断,可能是写满了 nwritten = 0; else return (-1);//其他错误,比如说网络错误 } nleft-=nwritten;//写成功 ptr+=nwritten; } return (n); }