知识整理:
再整理下多线程和多进程的理解,两者的基本概念就不谈了,记住基本的:
线程是CPU调度的基本单位,进程是资源拥有的基本单位
进程的创建对于不同的操作系统是不同的:
- 对于 Windows 系统来说,进程创建开销很大,因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。
- 对于 Linux 系统来说,进程创建开销很小,因此,Linux 下的学习重点是,大家要学习进程间通讯的方法。
可以做个实验:创建一个进程,在进程中往内存写若干数据,然后读出该数据,然后退出。此过程重复 1000 次,相当于创建/销毁进程 1000 次。在我机器上的测试结果是:
UbuntuLinux:耗时 0.8 秒
Windows7:耗时 79.8 秒
两者开销大约相差一百倍。
这意味着,在 Windows 中,进程创建的开销不容忽视。换句话说就是,Windows 编程中不建议你创建进程,如果你的程序架构需要大量创建进程,那么最好是切换到 Linux 系统。
大量创建进程的典型例子有两个,一个是 gnu autotools 工具链,用于编译很多开源代码的,他们在 Windows 下编译速度会很慢,因此软件开发人员最好是避免使用 Windows。另一个是服务器,某些服务器框架依靠大量创建进程来干活,甚至是对每个用户请求就创建一个进程,这些服务器在 Windows 下运行的效率就会很差。这"可能"也是放眼全世界范围,Linux 服务器远远多于 Windows 服务器的原因。
对于单进程多线程和多进程单线程的选择:
首先进程的创建开销比线程大很多,这个是普遍知道的常识,但是多核CPU切换时,进程的开销是否比线程的开销大很多呢?
事实上,二者的切换开销没有太大差别。本质上进程/线程在Linux内核实现上都是Task(或者叫Thread,在内核里是同一个东东),只不过同源多线程对应的Task共享的东西多一些(最主要是地址空间)。所以即便是多线程切换,页表也还是要切换的,只不过页表内容相同而已;同样,对于多线程切换,TLB该刷还是要刷的,因为对于CPU来讲多线程与多进程一样,都有不同的标识和不同的上下文,为了保证一致性,TLB刷新就省不了。
两个模型的差异在于,进程更安全,一个进程完全不会影响另外的进程。所以这也是 unix 哲学里推荐的编程方法;但是进程间通信比线程间通信的性能差很多,尤其是,如果这个是系统的关键部分,而又有大量数据的时候,所有的进程间通信方法都比线程间的通信慢很多
所以通常情况下推荐多进程程序,就像 nginx,一个 master 多个 worker,进程间只进行有限的通信(传递命令而非数据)
多线程的典型例子是 unbound,一个开源的递归 dns 服务器。它使用线程的理由也很充分:程序需要不停地向后方的授权 dns 请求数据,并传回给前方的模块。这个数据通信量大,性能要求又高,所以必须用多线程,如果是多个进程,那就要慢许多了。
CPU 的两个核心可以同时操作两个不同的进程,无需复制和交换上下文,因为两个进程不相干。
但如果两个核心同时操作两个不同的线程,则切换核心时需要把相关的上下文从一个核心同步到另外一个核心,因为两个线程需要共享很多东西。
现代的体系,一般 CPU 会有多个核心,而多个核心可以同时运行多个不同的线程或者进程。
当每个 CPU 核心运行一个进程的时候,由于每个进程的资源都独立,所以 CPU 核心之间切换的时候无需考虑上下文。
当每个 CPU 核心运行一个线程的时候,由于每个线程需要共享资源,所以这些资源必须从 CPU 的一个核心被复制到另外一个核心,才能继续运算,这占用了额外的开销。换句话说,在 CPU 为多核的情况下,多线程在性能上不如多进程。
因而,当前面向多核的服务器端编程中,需要习惯多进程而非多线程。
另外来说,开销这个问题存在误解。因为开销分多方面。时间开销跟空间开销。
就时间开销而言,Linux 下创建进程跟线程差不多,事实上创建线程的时间开销略微大一点点。
就空间开销而言,Linux 下创建进程总是有开销的,而且显然会略大于线程。
如果你需要不断的创建进程,做完事情之后,销毁进程,然后做下一件事情的时候再创建进程,做完事情之后销毁,进程数量在数百以内的级别,那么进程跟线程的效率是差不多的。这种情况下推荐使用进程。
如果你需要创建极大量的进程(成千上万这个级别),此时的效率跟创建大量的线程还是会有一定差别。如果这个差别已经大到了影响你的应用,那么你可以试着考虑使用线程。
一般来说,我认为这个差别不大的情况下,使用进程仍然更可取,因为程序的稳定性健壮性是第一位的,而性能与资源方面的开销往往可以通过提升服务器能力或者增加服务器解决。
总结:
多进程模型
优点
编程相对容易;通常不需要考虑锁和同步资源的问题。
更强的容错性:比起多线程的一个好处是一个进程崩溃了不会影响其他进程。
有内核保证的隔离:数据和错误隔离。
对于使用如C/C++这些语言编写的本地代码,错误隔离是非常有用的:采用多进程架构的程序一般可以做到一定程度的自恢复;(master守护进程监控所有worker进程,发现进程挂掉后将其重启)
多进程的案例
nginx主流的工作模式是多进程模式(也支持多线程模型)
几乎所有的web server服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached,httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程;
chrome浏览器也是多进程方式。
redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)
多线程模型
优点
多线程优点:创建速度快,方便高效的数据共享
共享数据:多线程间可以共享同一虚拟地址空间;多进程间的数据共享就需要用到共享内存、信号量等IPC技术;
适用的场景
1 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时);
2 提供非均质的服务(有优先级任务处理)事件响应有优先级;
3 单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延;
4 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)
多线程案例
桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程;
memcached
选用
单进程多线程和多进程单线程,2种模式如何取舍?
进程线程间创建的开销不足作为选择的依据,因为一般我们都是使用线程池或者进程池,在系统启动时就创建了固定的线程或进程,不会频繁的创建和销毁;
首先,根据工作集(需要共享的内存)的大小来定;如果工作集较大,就用多线程,避免cpu cache频繁的换入换出;比如memcached缓存系统;
其次,选择的依据根据以上多线程适用的场景来对比自身的业务场景,是否有这样场景需求:数据共享、提供非均质的服务,单任务拆散并行化等;
如果没有必要,或者多进程就可以很好的胜任,就多用多进程,享受单线程编程带来便利;
RCU的发明者,Paul McKenny 在《Is Parallel Programming Hard, And, If So, What Can You Do About It?》说过:
能用多进程方便的解决问题的时候不要使用多线程。
Reference:
1. https://www.zhihu.com/question/19901763
2. https://www.zhihu.com/question/19903801
3. http://www.cnblogs.com/me115/p/4866115.html