赞
踩
在了解多线程与多进程差异之前,我们先讨论一下摩尔定律。想必大家都有所了解,摩尔定律表示,处理器的时钟频率每两年提升一倍。多年来确实是按照这样的规律发展的,但最近开始有了一些变化,时钟频率的提升速度开始减慢。这也预示着,摩尔定律也许即将迎来终点,想想真是很可怕。
计算机制造商应对摩尔定律极限的方法,是通过引入多处理器。多核心架构是目前以及今后市场解决摩尔定律极限的主要策略。如今的工作站通常配备4、8、16、32、64个内核,利用多个内核来提高应用程序性能,保证及时响应,尤其在处理一些常见的高吞吐量工作负载时,如渲染,仿真,机器学习等耗时的高强度计算。
当我们想要最大化利用多个处理器的硬件结构,编写软件时选择正确的架构至关重要。在大多数情况下,我们可以选择多线程或多进程处理,又或者两者兼用。这个选择将影响软件的性能、后期的维护、可扩展性、内存等各方面。任何一种选择都有利弊,但熟悉各个选择,可以帮助我们做出正确的决定。在这篇文章中,我将解释不同应用软件开发选择多内核策略时需要考虑的因素。换句话说,厘清在各种应用场景中多线程与多进程开发的优劣。话不多说,我们直入主题。
多线程最突出的优点是借助变量、对象等,线程之间可以便捷地共享数据,与主线程进行通信也非常容易。
处理不可分割的大型数据集时,多线程开发也将非常有利。因为多进程处理需要复制数据集,导致占用大量时间和内存,同时使用共享内存数据会让软件开发更为复杂。
多线程另一个广为人知的特点是具有许多第三方库(开源和商业)的支持。如今许多数据库通过提供“线程安全”接口来支持多线程应用程序。组件、类、功能等预构建功能为多线程开发提供强大的支持,让开发人员的工作更为轻松,但物无完美……
多线程代码的主要缺点是,如果其中一个线程崩溃,整个应用程序将连带崩溃。与之相对,多进程中一个进程失败不一定影响其他进程。
另一个缺点是多线程应用程序调试困难。这个问题不容小觑,因为错误难以避免。通常,排错程序不是处理多线程错误的最佳工具,建议使用日志来跟踪错误并找出导致错误的线程(或线程之间的通信)。简单来说,调试时间会相对较长。同时,多线程应用程序需要经验丰富的开发人员进行开发和调试。因此如果团队成员经验不足,新手较多,也需要格外注意。
如果同时执行太多线程,也会出现另一个问题。处理器可能会花费大量时间进行上下文切换,而因此影响实际运行。文件系统占用大量内存块,导致 I\O 瓶颈,最终减慢整个应用程序,造成主机堵塞。
另外,还有内存问题。所有线程都使用相同的进程内存,这对于线程之间的通信是非常有利的。但是,如果每个线程需要更多内存,可线程内存又受限于进程内存空间。对比之下,这个问题在多进程开发中不存在,因为每个进程都有分配的内存空间。
谈到多进程处理,我们首先了解一下它的优势。
如前所述,一个进程崩溃,并不意味着整个应用程序的崩溃,这是多进程开发的一个显著优势(内核空间进程除外)。因此,如果某些进程失败,但编写的应用程序具有复原能力,开发就可以轻松恢复。
另一个优势是调试问题,我们现在了解到这也是多线程开发的劣势。在多进程中,调试要容易得多。比起调试同一进程内存空间中并行运行的多线程应用程序,处理一个小小的原子性进程就容易多了。
另外,锁的问题也会更少。但如果应用程序的进程实现类似于多线程架构(例如,使用相同的共享内存空间),这种情况下,多进程和多线程开发的复杂程度也大同小异了。尽管如此,倘若数据已进行备份(可在需要时合并),那么锁的问题也不再是个问题了。
最后,多进程是可扩展的。我们可以在其他地方执行进程,即利用远程机器或云分布处理,但线程总是限制在进程内存空间的上下文中。
不过,扩展并不能解决所有问题,多进程处理也有缺点。
通信是主要缺点。进程之间的通信比线程之间更为复杂。进程间的通信需要自定义开发才能共享数据并保持锁定和同步(必要时)。
此外,与线程安全数据库相比,支持多进程开发的数据库(我们称之为“进程安全”库)的数量相对较少。
了解了每种策略的优劣后,让我们深入探讨选择策略时需要考虑的各种因素,根据用户情况选择最佳方案,扬长避短。
在决定最合适的架构之前,需要考虑以下因素:
到目前为止,我们已经在理论上讨论了多线程和多进程之间的区别。为了真正理解这种差异,我们接下来将在真实地应用场景如 Maven vs. Make 中进行对比。
Maven 和 Make 都是流行和常见的构建工具。Maven 通常用于 Java 编译,而 Make 主要用于 C/C++ 编译。两者皆用于从小文件构建大型项目,且都有大量(数百甚至数千)编译任务,这些任务通常是原子性的,并且彼此独立。它们的通信很少,数据集也很小。
现在我们看一下在使用这些工具时如何选择并行处理架构。Maven 在单个构建上下文中是多线程的。几年前它是单线程的,但随着时间的推移它变成了多线程,这是一个积极的变化,让八个内核都能得到充分利用,而不仅仅局限在单一内核中。但是,多进程执行似乎对提高构建速度更有利。为什么?有几个原因:
首先,使用开源构建的开发人员通常需要编译开源代码。因此,项目将产生庞大的源代码,不论机器的性能多么强大,这个源代码都将拖累开发,减缓编译速度。这对编译时间、生产效率和上市时间都较为不利,特别是在转换到敏捷开发和持续集成时也有较大的影响。
另一个例子是 DevOps,DevOps 是需要包含更多任务的构建,如自动测试、代码分析,打包等,这使得编译速度更加缓慢。
最后,多进程结构允许进程分发,可以打破单个机器的限制,将编译的工作负载分发到多台机器同时执行,从而让构建更快且更具可扩展性。
与 Maven 相反,Make 构建系统架构是多进程的。进程分发只能应用于多进程开发,而不能应用于多线程;在多进程中,线程不能分布在主进程内存空间之外。每个进程都有自己的内存空间、环境变量等。事实上,大多数现代构建工具都是多进程而不是多线程的(例如 Visual Studio 的 MSBuild、CMake、Scons、Ninja、JAM、JOM、WAF 等等),原因是多进程的内存使用更为方便,开发和弹性扩展比较容易。
例如,在 Maven 或 Make 中使用八核机器执行 Qt 大约需要 16 分钟。但是,如果使用的是多进程架构,如 Make,则可以使用分布式计算工具(Incredibuild),远程执行所有编译任务(有效利用网络或公共云中的其他机器),将构建时间缩短到仅 1 分钟 40 秒!想想 1 分钟 40 秒,对比之前的 16 分钟是一个多大的突破!
当然,我们也可以看看其他的应用场景。
一种情况是需要同时存储所有数据的应用程序(例如,科学应用程序,天气预报,遗传算法等)。在这些情况下,具有超级计算能力的多线程是明智的选择。我们肯定不希望在远程机器上复制大量数据集,产生大量网络流量并拖累整个进程速度。在遗传算法等场景中,单个线程对数据的更改必须立即通过其他线程查看;同样的情况在分布式多进程结构中,则要求频繁同步数据,这将对性能产生极为负面的影响。
通常使用多线程的另一个现实场景是实时应用程序,例如金融交易,防御系统和汽车设备。这些场景无法等待进程初始化,通常需要实时响应。
数据库应用程序(如 CRM,ERP,SAP)是多线程使用的另一个例子。大多数数据库应用程序查询都是只读,少数需要写入操作。其原因是希望用户轻松地将数据集共享为进程空间的一部分,让其他计算线程可以轻松读取。
总结多线程最佳的应用场景:
现在让我们来看看适合多进程处理的一些场景。
第一个应用场景为,当我们具有庞大的独立数据集,数据实体之间的依赖性较低,并且我们不需要同时存储内存中的所有数据。
例如,金融衍生品,每种股票的每日计算。每个股票是一个不同的数据集,可以有一个单独的进程进行计算。另一个例子是渲染,数据分解为较小的数据块,每个电影帧将由不同的进程进行计算。
或者需要对小型业务部门进行许多计算时。我们以 Sarine Technologies 为例。Sarine 销售 HW 和软件,这些软件在原始钻石和其他宝石上运行大量的模拟实验,以找到最佳的切割方式,并获得最大收益。为了找到大型钻石的最佳切割方式,需要执行数百万个独立的并行模拟实验。Sarine 借助多进程开发的框架,将大量的模拟进程分配给网络中或公有云连接的机器,计算能力和效率大幅提升。
流媒体服务也往往是多进程的。例如,Nvidia Shield 是机顶盒装置,借助远程的高性能计算机,用户可以舒服地在沙发上玩高质量的图形游戏。OnLive 服务也是类似,用户能够在任何机器上安装 OnLive 应用程序。Netflix 是另一个不需要多余解释的例子。当进行远程输出时,我们可以始终使用负载平衡来确保足够的资源,同时后端进程可根据动态需求为终端用户提供服务。
借助当今公共云提供的无限容量,如果你的软件将来可能需要扩展,强烈建议考虑架构具有扩展到多个主机的能力。云服务还提供基于规模和使用情况的定价模型,可为你的软件提供额外的收入来源。
总而言之,在进行架构考虑并选择多线程或多进程时,大家可以问自己以下问题:
这些答案因人而异。本文只是简单引入一些相关问题进行讨论。在进行了25年多的管理和软件组织咨询工作后,我发现在开始编码之前进行上述考虑,能有效规避很多问题。随着产品的愈发成熟,变得更加复杂,产品开发对计算能力和可扩展性的需求也越来越大,这些提前的计划甚至会影响产品最终能否成功。
我希望上述的讨论,能为你的产品设计和开发带来更多灵感。
欢迎点击了解 Incredibuild 加速 C/C++ 构建编译的解决方案,并获取试用 License!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。