多线程还是多进程的选择及区别
1、鱼还是熊掌:浅谈多进程多线程的选择
在学习多进程与多线程方面,在教材中常引用的经典语句是'进程是资源分配的最小单位'、'线程是最小执行单位'这两句话。这些内容对于理解相关概念已经足够基础。然而,在实际工作中遇到选择题时容易混淆这两个概念而产生错误的理解导致难以解决相关问题影响工作效率。
经常在网络上看到有关XDJM们询问'选择多进程还是多线程更优?'、'在Linux环境下是采用多进程还是多线程更好?'诸此类问题时有耳闻。对此问题我的看法是:无高下之分只求更好。具体到不同场景下哪种方案更为合适便当具体情况具体分析便当高明之法也
我们从多个不同的维度进行分析,
以了解多线程与多进程之间的差异
(注:这种对比属于主观感受,
是一种较为理性的评估方式,
不强调某者表现有多么突出,
也不意味着另一方完全无优势)

1)需要频繁创建销毁的优先用线程
原因请看上面的对比。
最典型的应用场景莫过于Web服务器。
为每个连接创建一个线段。
一旦断开就会关闭该线段。
如果采用进程而非线段,则创建和销毁都需要付出较高的代价。
2)需要进行大量计算的优先使用线程
Intensive computations naturally require significant CPU resources. When becoming highly frequent, threads emerge as the most suitable choice.
这种原则最常见的是图像处理、算法处理。
3)强相关的处理用线程,弱相关的处理用进程
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
普通的服务器通常负责的任务包括通讯通道建立及数据包解析。其中,“通讯通道建立及数据包解析(即‘信息传输’)’与‘系统级事件监听’之间关联性较弱。而后者则进一步划分为两个子任务:一个是‘信息传输’(即通讯通道建立及数据包解析),另一个是‘系统级事件监听’。因此,在进程层面进行划分,在内核级进行分配。这两个子任务之间的关联度则要高得多。
当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。
4)可能要扩展到多机分布的用进程,多核分布的用线程
原因请看上面对比。
5)都满足需求的情况下,用你最熟悉、最拿手的方式
就数据共享与同步以及编程与调试等几个方面而言,在判断它们的复杂性与简单性时也需要权衡利弊。我也觉得很难确定哪种方案更优更好。不过我可以给你提个建议:如果多线程和 multiprocessing 都能满足需求的话,在熟悉程度上你更擅长哪个领域就优先选择哪个吧。
就数据共享与同步以及编程与调试等几个方面而言,在判断它们的复杂性与简单性时也需要权衡利弊。我也觉得很难确定哪种方案更优更好。不过我可以给你提个建议:如果多线程和 multiprocessing 都能满足需求的话,在熟悉程度上你更擅长哪个领域就优先选择哪个吧。
需要特别提示的是:虽然列举了诸多选择方案,并非所有场景都局限于单纯使用"进程+线程"的方式;建议在具体实施时充分考虑系统特性与应用场景需求,并避免误入非此即彼的思维误区。
消耗资源:
从内核角度来看,进程的主要职责就是承担分配系统资源(如CPU时间、内存等)的主要职责。子程序作为进程的一个运行序列,在CPU调度与分配中扮演着基础角色。它是比进程更小且能够独立运行的基础单元
这些代码基于相同的内存地址空间运行,并在虚拟机层面实现多态性与继承性机制;通过引入虚拟机栈的方式实现了对硬件资源的有效隔离;此外,在资源管理方面还实现了对虚拟化资源的最佳利用;这种设计使得虚拟化技术得以在实际应用中得到有效的实现;综上所述,在当前技术条件下实现高密度并行计算环境仍面临诸多挑战
通讯方式:
进程之间的数据传递仅能借助通讯机制完成,在实际操作中具有一定的延迟和不便。大部分线程时间数据能够实现跨线程共享(除了在线程函数内部),操作便捷高效。值得注意的是,在同步操作中尤其是涉及静态变量时需要特别谨慎
线程自身优势:
提升应用程序的响应速度;使得多核CPU系统运行更加高效。操作系统会确保当线程数量不超过 CPU 数量时(即线程数不大于 CPU 数目),每个线程能够独立运行在各自的 CPU 上;
优化程序结构。一个既长又复杂的进程既可以考虑划分为多个线程也可以选择分解为几个相对独立的部分这样的程序将有助于提高理解和维护性。
2、协程和线程区别:
异步函数是一种轻量级线程用于非阻塞IO处理
线程是进程的一个实例 ,扮演着CPU调度与分配的核心角色;它是比进程中更为精简的基本单元,在独立运行时展现出独特的优势。尽管单独运行时所需的系统资源较为有限(仅包含程序计数器、寄存器组以及栈等少量核心组件),但这些微薄的资源却能够与其他属于同一进程中的一系列线程协作完成更为复杂的任务。在线路间实现有效的信息传递主要依赖于共享内存机制,在操作速度上具有显著优势的同时也降低了不必要的硬件开销;然而由于其较之于进程中较为脆弱的数据保护机制,在发生不可预见状况时可能面临数据丢失的风险。
协程与线程的主要区别在于协程通过省去了不必要的调度任务而提升了性能;然而这种优化使得开发者不得不自行负责调度安排,并且协程也因此失去了基于标准段实现多核心处理的能力。
每个进程都管理着自己的专用内存区域,并且彼此之间互不共享内存空间;这些内存区域由操作系统进行分配与调度
每个线程都包含自己独立维护的栈以及一个与其它线程共享的堆;所有线程都能访问同一个堆空间,并且每个线程都拥有独立维护自己的栈;系统通过操作调度机制管理这些线程(传统多态性实现)。
当服务器必须应对大量并发请求连接时(例如同时有10000名用户试图连接),若采用线程作为执行单元并管理内部资源,则会导致绝大多数线程处于等待状态;然而引入协程后便实现了细粒度任务的动态分配(即让每个线程根据自身需求自主调度),从而避免陷入内核级别的上下文切换问题。这种情况下就促使协程应运而生。
这表明协程通过在单线程框架内完成调度任务的方式,有效规避了因内核层面的上下文切换而产生的性能损耗问题,并从而使得线程在IO操作方面的性能得到显著提升。
为什么协程不需要经过内核级别的上下文切换,我是这样认为的:
进程与线程均为操作系统内置的基础概念。协程则是在某些编程语言中具有原生支持。例如Go语言以及Lua等早期就已提供的框架类协同机制,则是一些则是随着软件版本的更新逐步添加。例如Python 2.5及其后的C#等语言亦然。
在py中:
每个线程都可以运行多个子任务,并且每一个进程也可以独立运行多个子任务;因此,在Python中就可以充分利用多核CPU。
- 线程进程都是同步机制,而协程则是异步
在每次协程重入时(也就是在每次过程被重新激活后),其运行状态与前一次调用保持一致
核心只有一个,线程是操作系统调度,协程是用户态调度。
协程不必是语言集成;例如,在C语言中可以通过setjmp和longjmp函数来实现协程;也可以通过手动调整ESP寄存器来完成换栈并实现协程。 协程本身与高吞吐无直接关联;基于I/O多路复用技术和回调机制能够有效地提升系统的并发能力和吞吐量;引入协程是为了将原本依赖回溯的异步操作转换为线性同步逻辑以提高执行效率与资源利用率。
3、基于多进程和基于多线程服务器的优缺点及nginx服务器启动过程
基于多进程服务器的优点:
1.由操作系统进行调度,运行比较稳定强壮
2.能够方便地通过操作系统进行监控和管理
比如观察各进程内存占用情况以及它们承担哪些Web请求进行监控。此外,通过设置信号量机制来实现对应用程序的各项管理功能。
3.隔离性好
一个进程出现问题只有杀掉它重启就可以,不影响整体服务的可用性
很容易实现在线热部署和无缝升级
不需要考虑线程安全问题
4.充分利用多核cpu,实现并行处理
基于多进程服务器的缺点:
1.内存消耗比较大,每个进程都独立加载完整的应用环境
2.CPU使用率过高,在高强度并发情况下各进程之间频繁地进行上下文切换,导致系统必须频繁进行内存页换操作
3.很低的io并发处理能力,只适合处理短请求,不适合处理长请求
基于多线程服务器的优点:
1.对内存的消耗小
线程之间共享整个应用环境,每个线程栈都比较小,一般不到1M
2.cpu上下文切换比较快
3.io的并发能力强
该系统能够轻松支持数百个并发线程切换操作,其处理能力远远超过多进程几十个并发进程的能力
4.有效利用多核cpu进行并行计算
基于多线程服务器的缺点:
1.不方便操作系统的管理
虚拟机对内存资源进行精细调度具有严格要求。垃圾回收算法的选择直接决定了系统的多线程处理效率及其吞吐能力。
3.若涉及对共享资源的操作,当系统发生线程死锁现象以及线程被阻塞时,可能导致整个应用无法正常运行
nginx的重启过程:
1.主进程重新加载应用环境
2.启动新的woker进程,同时向老的woker进程发送消息
旧woker进程接收到消息后停止接收新请求,并完成所有旧任务并退出系统
4.主进程收到新的请求后就交给新的woker进程处理
5、nginx是以多进程方式来工作的
Nginx采用了多进程中的一种工作模式,在其运行过程中会分配一个Master进程以及多个Worker进程来执行任务。
主节点负责协调管理从各个节点上传送过来的任务信息: 包括但不限于以下职责内容: 1) 按时响应外部触发事件并传递相关任务信息给所有从各个节点上传送过来的任务信息; 2) 持续监测从各个节点上传送过来的任务信息运行状态; 当某个节点出现故障退出时(特殊情况除外),系统将自动部署一个新的节点进行任务处理并重启相关服务
而基础网络事件则被分配到Worker进程中进行处理。各工件之间是平等的关系,并且任何一个请求都不会同时被多个Worker进程接收;同样地,在任何时刻最多只有一个Worker进程能够接收并处理同一个请求。工件数量通常与机器CPU核心数量相匹配;在我们配置80端口提供HTTP服务时,在线请求会不断涌向系统中各个可用资源点,并由相应的工件接收并完成这些请求的任务。
处理过程如下:
master会在准备好监听指定IP和端口的...后开始(通过fork机制生成子进程workers),这些子进程中继了父进程中所有的属性以及已准备好的... socket(这些属性中也包含父进程中已准备好的... socket),但每个子进程中拥有独立的... socket实例,并且都在相同IP地址和端口下运行——当一个新连接连接进来时会产生惊群现象。
通常情况下,在同一个socket上运行着多个进程时,在一个连接建立后时(即当一个连接进来后),这些进程都会收到通知信息;只有一个进程能够成功地接受这个连接请求,并且其余的都无法接受。
惊群现象: 一个文件描述符(fd)触发事件后被唤醒的是所有等待该fd的线程和进程。尽管如此,并非所有唤醒的线程和进程都会主动响应该事件。最典型的案例莫过于socket.accept操作:当多个用户进程或线程同时监听同一端口时,在socket.accept只能执行一次后就产生了惊群现象
Nginx对惊群现象的处理: nginx通过支持一个叫做accept_mutex的功能模块来解决惊群现象的问题。这相当于在accpet连接上加了一把互斥锁。有了这把互斥锁后,在同一时间段内只会有一个进程在处理连接请求,并因此避免了惊群问题的发生。该功能模块可以通过开关控制启用或禁用,并且默认情况下它是开启状态,并且可以通过配置关闭它。
worker进程工作: 当一个 worker 接收该连接后,在其可用资源范围内就开始接收并解析 incoming requests. 它会依次执行 receive, parse, process 和 generate 的操作. 在生成响应数据后发送给 client, 然后关闭该 connection. 整个 request 流程仅由单个 worker 进程负责处理.
小结:1)整个请求流程包括接收、解析和处理阶段,在完成各项操作后生成结果并反馈给客户端系统,并最终完成关闭连接流程。
2)一个完整的请求完全由一个worker进程处理。
好处:1)避免锁开销。每个Worker进程是独立运行的,并且无共享资源需求,在编排与调试过程中将更加便捷。 2)采用独立进程中减少了风险保障措施:一旦某个Worker进程中出现故障或退出,则其他Worker进程中仍能正常运行,并确保服务系统不会受到影响;Master能在较短时间内重新启动并重启新的Worker进程中以维持服务连续性。此外,在发生Worker异常退出的情况下(如程序逻辑错误导致),只会使得当前工件处理失败而对其他工件不影响整体系统稳定性提供了一定保障
Nginx的事件处理机制:在基本Web服务器中,所有应用都会涉及几种类型的事件:网络消息、中断以及时间戳。基于异步非阻塞模型,在多线程机制下管理预设事件序列可显著提升系统吞吐量并保持极低资源消耗。
以epoll为例:在事件未做好准备的情况下,在系统中被放入epoll队列中等待处理。一旦某个事件准备好后,则会进入处理流程;如果某个事件返回了EAGAIN错误码,则会继续将其放入epoll队列中等待再次处理。因此,在所有请求都已做好准备的情况下(即单个线程负责切换请求),就可以并行处理大量的并发请求(实际上由于是单个线程负责切换请求)。这种机制使得即使只有一个线程负责切换请求间切换的任务(因为异步操作),但也能持续不断地为多个请求提供服务。这种无阻塞多路复用的方式下,在系统资源有限的情况下实现了尽可能多的并发事务处理能力。
问题1:基于Nginx采用了worker进程来处理请求,在这种架构下每个 worker 进程仅有一个主线程线,在 worker 子进程中实现多个并发处理的能力
那么能够处理的并发数有限。概括的讲,Nginx如何实现高并发?
回答1:采用了异步非阻塞式的事件处理机制。因为采用了异步非阻塞方式的原因,在进程上轮换执行多个已准备好接收处理的事件。例如,在epoll机制下,默认情况下会将所有待处理的事件都放入到epoll缓冲区中,并且当有新的事件准备好时就会立即开始执行相应的任务来完成这些请求的操作。
问题2:如何实现非阻塞异步方式
参考文档:http://www.ibm.com/developerworks/cn/linux/l-async/
问题3:Nginx与Apache在高并发处理方面的区别是什么?
问题4:为何推荐worker的个数为cpu的个数?
回答4:因为过多的worker数量会导致进程之间竞争CPU资源,并引起不必要的上下文切换
参考 ... 初步探讨Nginx架构
对Apache进行对比分析:采用事件驱动模式更适合处理那些具有较高I/O负载的任务;而针对主要依赖CPU资源的系统而言,则建议采用多线程或进程模式以提高性能效率。
1)Nginx主要以反向代理充当而非Web服务器使用。其网络模式基于事件驱动机制(select、poll、epoll)。
2)尽管如此,在本质层面上它依然属于基于IO的事件处理体系。
3)从功能特性来看,在面对那些对IO性能要求较高的任务时,Nginx表现得最为出色。
4)采用事件驱动模式进行反向代理能够显著提升性能。一个工作进程即可独立运行而不产生进程、线程管理开销。
5)当然,在某些情况下也可以采用多进程结合libevent的方式实现高效的负载分配。
6)虽然静态资源通常涉及较低复杂度的操作(如磁盘IO),但Nginx在处理这些资源时依然表现出色。
随手编写一个网络程序能够满足处理几万个请求的并发需求;但如果大部分客户端长时间被阻塞在那里,则没有实际价值。
了解Apache和Resin这类Web服务器为何被称为Web服务器的原因在于它们确实运行着各种具体的应用程序。例如科学计算图形处理以及数据存储操作等各类业务流程都需要依赖这些系统来完成。
它们很可能是CPU密集型的服务,事件驱动并不合适。
1)比如某个运算所需时间持续为2秒,在这种情况下(比如进行一次大型join或sort操作),所有客户端都会被长时间地封锁。
想想如果我们把MySQL改为基于事件驱动的架构会发生什么呢?在这样的情况下(比如进行一次大型join或sort操作),所有客户端都会被长时间地封锁。
这个时候多线程或多进程便能展现其优势。
各个进程之间相互独立地执行各自的任务,
彼此之间不会互相阻塞也不会造成干扰。
当然. 现代处理器的速度日益提升.
单个运算出现短暂的瓶颈问题则可能不会对整体性能产生显著影响.
但是如果有任何一个运算出现长时间的瓶颈问题则可能会对整体性能产生影响.
事件编程并无明显优势。因此进程和线程等技术并不会消亡。综上所述,在处理IO密集型服务时采用事件驱动更为合适。而并行处理类的技术更适合处理CPU密集型服务
