并发编程:进程、IO 多路复用、线程

00 并发(concurrent)

并发:逻辑控制流在时间上有重叠,换言之,两个相对独立的逻辑控制流在时间上不是严格的先后次序,而是有一次或多次的交替。

应用级并发程序称为并发程序。现代操作系统提供了三种基本构造方法:

01 基于进程的并发编程

使用进程构造并发是最简单的。

以并发 Web 服务器为例。父进程负责监听一个监听描述符,在有连接请求时,会创建一个子进程,父子进程间拷贝监听描述符和已连接描述符。我们的目标就是让父进程只只负责监听,让子进程只负责处理 已连接 socket 的通讯,所以需要在父进程关闭已连接描述符,在子进程关闭监听描述符。

要注意一点的是,在父进程中关闭已连接描述符至关重要,否则将会很快耗尽系统资源,因为若父进程不主动关闭已连接描述符,子进程即使通信结束,也无法关闭父进程已连接描述符。

另外,还有一点必须考虑并正确妥当地处理好:回收僵死进程。这是因为服务器的目标一般是要运行超长时间,在运行期间会不可避免地出现僵死进程。

总结起来,基于进程的并发 Web 服务器,是通过共享文件表实现,我们只需要处理好描述符的管理即可,具体调度由内核负责。因为这样,基于进程的并发在实现起来比较容易,逻辑也比较简单,但进程控制和共享信息的 IPC 机制的开销都很高,所以往往比较慢。

关键概念是:描述符,文件表,内核调度,IPC。

02 基于 IO 多路复用的并发编程

基本思路是使用 select 函数,要求内核挂起进程,只有在一个或多个 IO 事件发生后,才将控制返回给应用程序。示例如下:

select 函数是一个复杂的函数,有很多使用场景。此处只涉及一个场景:等待一组描述符准备好读。

select 函数一直阻塞,直到 read_set 中有描述符变为可读,之后调用相应的处理函数。

IO 多路复用可以作为事件驱动程序的基础,在事件驱动程序中,逻辑流是因为某件事情而前进的。一般是将逻辑流模型化为状态机,粗略而言,状态机由一组 state、input event 和 transition 组成,其中 transition 就是将 state 和 input event 映射到 state。

状态机可以用有向图来表示,其中节点表示状态,有向弧表示转移,弧上的标号代表输入事件。

IO 多路复用下的并发给了程序员对于程序更强的控制力,同时由于在一个进程下,效率提升明显,但是一个明显的缺点是编码复杂,代码量大且不容易控制逻辑流的粒度大小。此外,不能很好地利用多核处理器。

核心:select 检测可读描述符到待读描述符的状态变化,以此为基础划分状态机。状态机的三个组成,状态,输入事件,转移。在转移中运行业务逻辑流的同时将当前状态和输入事件映射到对应状态,触发下一个状态机里的转移。

03 基于线程的并发编程

现代操作系统中,允许同一个进程上下文中存在多个线程。事实上,进入一个进程后,会首先创建一个主线程,不同于进程间严格的父子关系,主线程与其创建的子线程之间是对等关系,共同存在于一个线程池中,线程之间的切换也是由内核负责,但不同于多进程并发,同一个进程下的多个线程虽有自己的 TID(Thread ID)、栈、栈指针、程序计数器、通用目的寄存器和条件码,但会共享地址空间内的所有内容,包括代码、数据、堆、共享库和打开的文件。

一个线程上下文要比一个进程上下文小得多,切换也快得多。对等线程概念下,一个线程可以杀死对等线程,也可以等待对等线程终止。再次说明,每个对等线程都能读写相同的共享数据。

多线程编程中的基础概念:


不知是该恭喜,还是该怎样,总之阅读到该文的,你是第 人。每一次刷新,都是不同的自己。