2.4 异步、并发

异步

应用程序及内核

Linux操作系统在设计上将虚拟空间划分为用户空间和内核空间,两者做了隔离是相互独立的,用户空间给应用程序使用,内核空间给内核使用。内核具有最高权限,可以访问受保护的内存空间,可以访问底层的硬件设备。而这些是应用程序所不具备的,但应用程序可以通过调用内核提供的接口来间接访问或操作。所谓的常见的IO模型就是基于应用程序和内核之间的交互所提出来的。以一次网络IO请求过程中的read操作为例,请求数据会先拷贝到系统内核的缓冲区(内核空间),再从操作系统的内核缓冲区拷贝到应用程序的地址空间(用户空间)。而从内核空间将数据拷贝到用户空间过程中,就会经历两个阶段:

  • 等待数据准备
  • 拷贝数据

也正因为有了这两个阶段,才提出了各种网络I/O模型。

同步和异步

同步和异步的概念描述的是应用程序与内核的交互方式,同步是指应用程序发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行;而异步是指应用程序发起I/O请求后仍继续执行,当内核I/O操作完成后会通知应用程序,或者调用应用程序注册的回调函数。

阻塞和非阻塞

阻塞和非阻塞的概念描述的是应用程序调用内核IO操作的方式,阻塞是指I/O操作需要彻底完成后才返回到用户空间;而非阻塞是指I/O操作被调用后立即返回给用户一个状态值,无需等到I/O操作彻底完成。

常见的网络IO模型

  1. 同步阻塞IO(Blocking IO)

    进程发出IO请求后,进入阻塞状态,直到内核返回数据,才重新继续执行

  2. 同步非阻塞IO(Non-blocking IO)

    进程发出IO请求后,不阻塞,如果数据没准备好,直接返回错误

  3. IO多路复用(IO Multiplexing)

    IO复用阻塞在select、poll或epoll这样的系统调用上,通过这种方式在不使用多线程的前提下,单个进程可以同时处理多个网络连接的IO

  4. 异步IO(Asynchronous IO)

    在一个进程发出IO请求后直接返回,内核在整个操作(包括数据复制都进程缓存区)完成后通知进程

并行

在操作系统中,一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并发和并行的区别:

  • 并发(concurrency):逻辑上具备同时处理多个任务的能力
  • 并行(parallesim):物理上在同一时刻执行多个并发任务,依赖多核处理器等物理设备

并发编程模型

多进程

进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。多进程是在操作系统层面进行并发的基本模式。同时也是开销最大的模式。在Linux平台上很多工具链正是采用这种模式在工作。比如某个Web服务器,它会有专门的进程负责网络端口的监听和链接管理,还会有专门的进程负责事务和运算。这种方法的好处在于简单、进程间互不影响,坏处在于系统开销大,因为所有的进程都是由内核管理的。

多线程

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。多线程在大部分操作系统上都属于系统层面的并发模式,也是我们使用最多的最有效的一种模式。目前我们所见的几乎所有工具链都会使用这种模式。它比多进程的开销小很多,但是其开销依旧比较大,且在高并发模式下效率会有影响。

基于回调的非阻塞/异步IO

这种架构的诞生实际上来源于多线程模式的危机,在很多高并发服务器开发实践中,使用多线程模式会很快耗尽服务器的内存和CPU资源。而这种模式通过事件驱动的方式使用异步IO,使服务器持续运转,且尽可能地少用线程,降低开销,它目前在Node.js/PHP Swoole扩展中得到了很好的实践。但是使用这种模式,编程比多线程要复杂,因为它把流程做了分割,对于问题本身的反应不够自然。

协程

协程英文“coroutine”,协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。协程本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中。因此,系统开销极小,可以有效提高线程的任务并发性,而避免多线程的缺点。使用协程的优点是编程简单,结构清晰;缺点是需要语言的支持,如果不支持,则需要用户在程序中自行实现调度器。目前,原生支持协程的语言还很少。

links

results matching ""

    No results matching ""