Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

再次理解同步异步和阻塞非阻塞 #11

Open
AlexiaChen opened this issue Oct 16, 2019 · 0 comments
Open

再次理解同步异步和阻塞非阻塞 #11

AlexiaChen opened this issue Oct 16, 2019 · 0 comments
Labels
并发与并行编程 高并发,高性能,多核相关的理论 操作系统 操作系统理论和实现 计算机网络 TCP/IP 网络调试,网络架构等等

Comments

@AlexiaChen
Copy link
Owner


title: 再次理解同步异步和阻塞非阻塞
date: 2017-07-27 09:30:35
tags:
- IO
- 同步异步
- 阻塞非阻塞

唉,涉及到网络IO和文件IO的时候又把这些概念混淆了。


从编程角度阐释

异步与同步与单线程或多线程都无关,是以任务的执行顺序有关,是更加高层次的抽象概念,下面以线程举例子:

同步(一个任务的开始必须依赖另一个任务的完成)

  • 单线程:
    1 thread: / <- Task 1 -> / |
                               | / <- Task 2 -> / |
                                                  | / <---- Task 3 ----> / |
  • 多线程:
thread A: -> / <- Task 1 -> / |
thread B: ---------------->   | -> / <--- Task 2 ---> / |
thread C: -------------------------------------->       | -> / <- Task 3 -> /

异步(一个任务的开始不必依赖另一个任务的完成)

  • 单线程:
1 thread : / <---------- Task 1 ----------> /
                           / <---- Task 2 ---> /
                   / <---- Task 3 ----> /
  • 多线程:
thread A: -> / <- Task 1 -> /
thread B: -----> / <-------- Task 2 --------> /
thread C: -----------> / <- Task 3 -> /

所以说,单线程也可以异步,多线程也可以同步。在多线程同步中,一个线程需要等待另一个线程的完成,所以会阻塞当前线程(操作系统会挂起当前线程),也就是多线程同步过程中,一般会有阻塞。单线程同步一般就不会有阻塞,所以在Socket编程中,在创建socket的时候,一般有block和non-block选项,系统会为了等待数据阻塞当前等待数据的线程,并由其他读取数据完毕的线程唤醒等待的recv线程,而继续运行。如果是选择non-block,那么等待数据的线程不会被挂起,无论网络数据是否到达会继续recv返回执行,需要调用者不停地轮询数据是否到达,到达再读取。

block和non-block也只是概念,它的底层大部分是由锁来实现的,是锁概念的延伸和高层次的概念映射,比如读写锁,如果写锁被上锁了,那么读操作会被阻塞(读线程被挂起),等到写锁释放,才能继续读。所以,在谈到block和non-block的时候就会涉及到多线程的概念,肯定底层会有多线程的操作。单线程是不可能有阻塞和非阻塞的概念的。

当然,有人说node.js中的readFile和readFileSync就分别是是异步非阻塞和同步阻塞。而node是单线程的为什么会有block和non-block的概念?因为涉及到阻塞和非阻塞一般是底层可能会有多线程处理IO的操作,nodejs的应用层面是看不到的,因为阻塞的线程是不会自动醒来的,必须要另一个线程来唤醒(Resume)以提示IO的完成。也就是nodejs的应用代码确实是单线程执行的,但是在发起文件IO的时候,肯定会有个IO线程来对文件进行读写,最后通知应用层的js代码。其实以上的说法是不准确的,因为对于文件读写是没有非阻塞这以说法的,因为底层肯定会有阻塞操作等待数据读进缓冲区,还是有锁来阻塞。所以在谈及文件IO的时候我们一般只谈及同步和异步,在谈及网络IO的时候只谈及阻塞和非阻塞。

当然我也觉得同步异步和阻塞非阻塞不能单从字面意义上理解。同步和异步关注的是消息通信机制,所谓同步就是caller主动等待callee的结果,也就是说在没有得到调用结果之前,callee就不返回,一旦callee返回了,一定得到返回结果了。异步正好相反,callee被调用之后,caller对于callee的调用就直接返回了,callee通过状态,或者消息,回调函数等机制来把调用的处理结果通知给caller。

阻塞和非阻塞关注的是caller在等待调用结果(返回值,消息等)时的状态,也就是caller当时的状态。阻塞调用是指callee的被调用结果返回之前,当前的caller线程会被挂起,只有得到结果了,callee才会返回,caller线程被唤醒并继续执行。非阻塞调用是指callee不能得到结果之前,对callee的调用不会阻塞caller的当前线程。

据知乎上的陈硕所说,在处理 IO 的时候,阻塞和非阻塞都是同步 IO。只有使用了特殊的 API 才是异步 IO。

怎样理解他这句话呢?《Unix网络编程》这大部经典著作其中有段原文:

POSIX defines these two terms as follows:

  • 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.

Using these definitions, the first four I/O models—blocking, nonblocking, I/O multiplexing, and signal-driven I/O—are all synchronous because the actual I/O operation (recvfrom) blocks the process. Only the asynchronous I/O model matches the asynchronous I/O definition.

简单来说就是,对Unix-Like的系统来讲:阻塞式I/O(默认),非阻塞式I/O(nonblock),I/O复用(select/poll/epoll)都属于同步I/O,因为它们在数据由内核空间复制回进程缓冲区时都是阻塞的(不能干别的事)。只有异步I/O模型(AIO)是符合异步I/O操作的含义的,即在1数据准备完成、2由内核空间拷贝回缓冲区后 通知进程,在等待通知的这段时间里可以干别的事。

Reactor模式和Proactor模式

通常我们谈论事件驱动思想的时候会提到这两个模式,那么这两个模式到底有什么区别呢?

在Unix标准中的同步IO中的非阻塞IO,也就是NIO(non-block IO),就是基于事件驱动思想的,实现上采用Reactor模式,从程序角度而言,当发起IO的读或写操作时,是非阻塞的;当Socket有流可读或可写入Socket时,操作系统会相应地通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。对于网络IO而言,主要有连接建立、流读取及流写入三种事件,Linux 2.6以后的版本采用epoll 方式来实现NIO。

对于另一种异步IO,也就是AIO(asyn IO),同样是基于事件驱动的思想,实现上通常采用Proactor模式。从程序角度而言,和NIO不同,当进行读写操作时,只需要直接调用API的read或write方法即可。这两种方法均为异步,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序。对于写操作而言,当操作系统将write方法的流写入完毕时,操作系统主动通知应用程序。与NIO比较而言,AIO一方面简化了程序的编写,流的读取和写入都由操作系统来代替完成,另一方面省去了NIO中程序遍历事件通知队列的代价。windows基于IOCP实现了AIO,对,IOCP就是异步的,但是Linux目前只有基于Epoll模拟实现的AIO。显然,Linux从Epoll到AIO有一层转换,就是用同步的Epoll来模拟异步,Epoll是Reactor模式,AIO是Proactor模式。 所以这两个模式的关系,你可以简单理解为,Proactor模式是同步Reactor模式的异步体现,Proactor模式的层次更高,简化了程序编写。从复杂度来将,Reactor更反人类,因为Reactor是通知程序员有数据可以读写了,程序员再主动读写数据。Proactor模式是数据读写完毕了,再主动通知程序员。

需要注意的一点就是Reactor模式一般是单线程实现,几乎所有的Reactor实现的系统都是单线程的,尽管Reactor模式也可以工作在多线程环境下,因为Proactor模式是Reactor的异步形式的变种,所以Proactor模式一般也是单线程实现的,而非多线程。

@AlexiaChen AlexiaChen added 并发与并行编程 高并发,高性能,多核相关的理论 计算机网络 TCP/IP 网络调试,网络架构等等 操作系统 操作系统理论和实现 labels Oct 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
并发与并行编程 高并发,高性能,多核相关的理论 操作系统 操作系统理论和实现 计算机网络 TCP/IP 网络调试,网络架构等等
Projects
None yet
Development

No branches or pull requests

1 participant