1. 引言
只有真正写过操作系统内核的人,比如大神Linus才能真正理解进程和线程的区别与联系,像我们学过操作系统课程的人都知道进程和线程的基本概念,但是仅仅是懂个皮毛而已。所以我也是纸上谈兵,根据理论知识加上自己多年的编程实践和项目上的经历,以及最近做的一些实验,总结一下如何通过多进程、多线程模型来提高应用系统的性能,比如对于开源的软交换FreeSwitch。

2. 函数、程序、进程和线程
计算机科学中一个函数可以理解为对于输入数据的运算,并输出结果,这个和数学中的函数概念类似。程序是一组函数的集合,一般包含一个主函数,是整个程序的入口。进程就是一个程序在某个数据集上的一次运行,或者说针对某个输入数据集,程序的运算过程就是进程,是计算机操作系统对资源分配的最小单元。线程是操作系统能够进行运算调度的最小单元,它被包含在进程之中,一个线程指的是进程中一个单一顺序的函数执行序列,在多核系统上多个线程可以并发执行、交替执行、顺序执行,线程之间可以共享所在进程内部的所有资源,而进程之间是相互独立的,进程之间通信需要相对复杂的方法,而线程之间通信简单轻量一些。

3. 分治法
在计算机科学中,分治法是一种很重要的算法模型。就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题解的合并。这个技巧是很多高效算法的基础,比如:快速排序、归并排序、快速傅立叶变换等等。举一个简单的例子,计算表达式:(1+2)*(3+4),1+2、3+4作为两个独立的子任务,分别在两个子任务中进行计算,得到3和7,然后再执行子任务3*7。子任务可以用线程或者进程去完成,分别对应于多线程和多进程开发模型。当然也可以不进行分解运算,直接在一个线程中完成上述3个步骤,只不过要借助于堆栈这种数据结构。对于不同的计算问题,到底是否需要使用分治法,取决于问题分解的开销和分治法减少的开销,比如矩阵运算、随机优化算法、排序算法等等,都很适合用分治法进行并行计算从而加快运算速度。

4. 多进程
在一个或者多个计算机上,运行同一个程序的多个实例,分别处理不同的数据集,这就是多进程。比如:启动多个FreeSwitch进程,分别处理不同客户的通话数据(信令和媒体流)。我做了一个多进程的实验,程序的功能非常简单:对于收到的UDP数据不做任何加工处理,直接回复给客户端,硬件信息和测试程序截图如下。


压测结果如下图,可以看出CPU、CPS和并发进程数量成线性关系,基本上达到了理想的线性加速比,由此可见多进程对于FreeSwitch这种UDP处理的任务可以很好的提高吞吐率。当然也不是说进程启动越多越好,由于操作系统要对进程进行调度(选择不同的进程占用CPU进行运算),同一台机器上进程数量过多反而降低了系统的吞吐率,一般来说启动进程数量小于等于机器上CPU核数。


5. 多线程
在同一个进程内部,启动多个线程进行运算,比如:FreeSwitch内部有处理信令的线程(所有通话使用同一个信令处理线程),每个通话使用一个独立的媒体流处理线程,也就是1+N的模式。不同通话的线程相对独立,可以运行在不同的CPU内核上,从而提高CPU利用率,进而提高FreeSwitch的吞吐率。当然也不是说线程越多越好,线程数量过多会导致系统对线程的调度开销太大,对于媒体流处理线程,G711编解码每秒是50个数据包,如果频繁的线程调度会导致编解码效率的降低,从而导致出现丢包、声音颤抖等问题。一般而言一个FreeSwitch进程支持300左右的并发通话,占用3个左右的内核是合适的,如果让FreeSwitch支持更高的并发通话,占用过多的内核会导致系统长期疲劳从而增加不稳定性。

6. 结论
多进程和多线程并不矛盾,二者解决问题的层次是不一样的。多线程是解决单个进程提升性能的问题,在进程内部把某些可以并行运算的任务放到不同的线程中执行。但无论如何优化,单个进程总有一个性能上限,这个时候就需要使用多进程模式来提升整个系统的能力,一般叫做负载均衡。举一个例子:单个FreeSwitch支持20CPS、300并发通话,但是我们在FreeSwitch前面增加了负载均衡器之后,单个集群可以支持到1200CPS、18000并发通话,并且集群又可以在更高层次上线性扩展,比如:分地区部署不同的集群,实现多站点并行异地多活模式。

声明:文中部分图片来自于互联网,版权归原作者所有,如对您的知识产权有任何侵权请立即通知我删除。