一、应用级并发的作用
构造并发程序的方法:
二、基于进程的并发编程
构造并发程序最简单的方法就是用进程,使用像fork、exec和waitpid之类的函数。
特点:
父子进程之间共享文件表,但是不共享用户地址空间。
优点:一个进程不会覆盖另一个进程的虚拟内存。
缺点:进程共享状态信息变得困难,必须使用显式的IPC(进程间通信)机制。
结果就是这种设计方式比较慢,因为进程控制和IPC的开销很高。
三、基于I/O多路复用的并发编程
I/O多路复用(I/O Multiplexing)技术基本的思想就是使用select函数,要求内核挂起进程,只有在一个或者多个I/O事件发生后,才将控制返回给应用程序。
I/O多路复用可以用作并发事件驱动(event-driven)程序的基础。
在事件驱动程序中,某些事件会导致流向前推进。
一般的思路是将逻辑流模型化为状态机(state machine):
- 它是一组状态(state)、输入事件(input-event)和转移(transition)。
- 转移是将一个(输入状态,输入事件)对映射到一个输出状态。
通常把状态机画成有向图:
这种设计的优点有:
- 它比基于进程的设计给了程序员更多的对程序行为的控制。
- 它是运行在单一进程上下文中,因此每个逻辑流都能访问该进程的全部空间地址。这使得在流之间共享数据变得很容易。
- 比进程设计方式要高效很多,因为它不需要进程上下文切换来调度新的流。
这种设计的缺点有:
- 编码复杂。
- 不能充分利用多核处理器。
现代高性能服务器(比如node.js,nginx和tornado)使用的都是基于I/O多路复用的事件驱动的编程方式,主要是因为相比于进程和线程的方式,它有明显的性能优势。
四、基于线程的并发编程
线程(Thread)就是运行在进程上下文中的逻辑流:
- 它由内核自动调度。
- 它有自己的线程上下文(thread context)。
- 所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。
基于线程的逻辑流结合了基于进程和基于I/O多路复用的流的特性。
- 每个进程开始生命周期时都是单一线程,这个线程成为主线程(main thread)。
- 在某一个时刻,主线程创建一个对等线程(peer thread),从这个时间点开始,两个线程就并发地运行。
- 最后,因为主线程执行一个慢速系统调用,或者被系统的间隔计时器中断,控制就会通过上下文切换传递到对等线程。
对等线程执行一段时间,然后控制传递回主线程。
线程不同于进程在于:
- 线程的上下文很小,所以切换线程上下文比切换进程的要快得多。
- 线程没有严格的父子层次来组织,主线程和对等线程的区别仅在于它是进程中第一个运行的线程。
- 一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止。
每个对等线程都可以读写相同的共享数据。 - 在任意一个时间点上,线程是可结合的(joinable)或者是可分离的(detached)。
- 一个可结合的线程能被其他线程回收和杀死,在被回收之前,它的内存资源不会释放。
- 一个分离的线程是不能被其他线程回收或杀死的。它的内存资源在它终止时由系统自动释放。