构造并发程序的三种基本方法和优缺点
构造并发程序的三种基本方法
进程
用这种方法,每个逻辑控制流都是一个进程,由内核来调度维护.因为进程有独立的虚拟地址空间,想要和其他流通信,控制流必须使用某种显式的进程间通信机制.
I/O多路复用
在这种形式的并发编程中,应用程序在一个进程的上下文中显式地调度它们自己的逻辑流.逻辑流被模型化为状态机,数据到达文件描述符后,主程序显式地从一个状态转换到另一个状态.因为程序是一个单独的进程,所以所有的流都共享同一个地址空间.
假设要求编写一个echo服务器,它也能对用户从标准输入键入的交互命令做出响应.在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起连接请求,2)用户在键盘上键入命令行.我们先等待哪个事件呢?没有哪个选择是理想的.如果在accept中等待一个连接请求,我们就不能响应输入的命令.类似的,如果在read中等待一个输入命令,我们就不能响应任何连接请求.
针对这种困境的一个解决办法就是I/O多路复用技术.基本的思路就是使用select函数,要求内核挂起进程,只有一个或多个I/O事件发生后,才将控制返回给应用程序.
线程
线程是运行在一个单一进程上下文中的逻辑流,由内核进行调度.你可以把线程看成是其他两种方式的混合体,像进程流一样由内核进行调度,而像I/O多路复用流一样共享同一个虚拟地址空间.
在一些重要方面,线程执行是不同于进程的.因为一个线程的上下文要比一个进程的上下文小得多,线程的上下文切换要比进程的上下文切换快得多.另一个不同就是线程不像进程那样,不是按照严格的父子层次来组织的.和一个进程相关的线程组成一个对等(线程)池,独立于其他线程创建的线程.主线程和其他线程的区别仅在于它总是进程中第一个运行的线程.对等(线程)池概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止.另外,每个对等线程都能读写相同的共享数据.
构造并发程序的三种基本方法的优劣
进程的优劣
在父进程与子进程间共享状态信息,进程有一个非常清晰的模型: 共享文件表,但是不共享用户地址空间.进程有独立的地址空间既是优点也是缺点.这样一来,一个进程不可能不小心覆盖另一个进程的虚拟地址,这就消除了许多令人疑惑的错误--这是一个明显的优点.
另一方面,独立的地址空间使得进程共享状态信息变得更加困难.为了共享信息,它们必须使用显式的IPC(进程间通信).基于进程的设计的另一个缺点是,它们往往比较慢,因为进程控制和IPC的开销很高.
I/O多路复用的优劣
事件驱动设计的一个优点是,它比基于进程的设计给了程序员更多的对程序行为的控制.
另一个优点是,一个基于I/O多路复用的事件驱动服务器是运行在单一进程上下文中的,因此每个逻辑流都能访问该进程的全部地址空间.这使得流之间共享数据变得很容易.一个与作为单个进程运行相关的优点是,你可以利用熟悉的调试工具来调试你的并发服务器,就像对顺序程序那样.最后,事件驱动设计常常比基于进程的设计要高效很多,因为他们不需要进程上下文切换来调度新的流.
事件驱动设计的一个明显缺点就是编码复杂,随着并发粒度的减小,复杂性还会上升.基于事件的设计另一个重要缺点是它们不能充分利用多核处理器.