多进程模式的服务器端实现相比于 select 和 epoll 有自身的优化,但同时也有很大的问题。比如,创建进程(复制)的工作本身会给操作系统带来相当沉重的负担。而且每个进程具有独立的内存空间,所以进程间通信的实现难度也会随之提高。可以概括如下:
- 创建进程的过程会带来一定的开销。
- 为了完成进程间的数据交换,需要特殊的IPC技术。
最致命的缺点则是,进程间的上下文切换(Context Switching)。
有的人会问,只有1个CPU的系统中不是也可以同时运行多个进程吗?这是因为系统将CPU时间分成了很多微小的块,然后分配给多个进程。为了分时使用CPU,需要“上下文切换”。即运行程序前需要将相应的进程信息读取到内存中,如果运行进程A后需要紧接着运行进程B,就会将进程A的相关信息移出内存,并读入进程B的相关信息,这就是上下文切换。索引上下文切换需要很长的时间,存在一定的局限。
为了保持多进程的优点,同时在一定程度上克服其缺点,人们引入了线程(Thread)。这是为了将进程的各种劣势降到最低(不是直接消除)而设计的一种“轻量级进程”。
- 线程的创建和上下文切换相比进程的创建和上下文切换要更快
- 线程间的数据交换无需特殊的技术
每个进程的内存空间都由保存全局变量的“数据区”、像malloc等函数动态分配的的堆(heap)空间、函数运行时的栈(stack)空间构成。
但是,如果为了获得多个代码的执行流,则不应该完全分离上述内存结构,而只需分离栈区域,通过这种分时有如下优势:
- 上下文切换时不需要切换数据区和堆
- 可以利用数据区和堆交换数据
实际上这就是线程。线程为了保持多条代码的执行流和隔离了栈区域。
多个线程共享进程内的数据区和堆,为了保持这种结构,线程将在进程内创建并运行
。进程和线程的定义如下:
- 进程:在操作系统构成单独执行流的单位
- 线程:在进程内构成单独执行流的单位
如果是进程在操作系统内部生成多个执行流,那么线程就是在一个进程内部创建多个执行流。因为操作系统、进程、线程之间的关系可以用如下图表示: