彻底理解 进程、线程、多进程、多线程

简介: 面试经常被问到的问题,今天给大家总结一下📌

进程和线程


进程

教科书上解释:进程是资源分配的最小单位

👀可以理解为:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文。进程的生命周期有调入,执行,保存的过程。


线程

教科书上解释:线程是CPU调度的最小单位

👀可以理解为:一个应用程序的执行可能有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块;这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,单核任务中划分更为细小的CPU时间段。


进程和线程的区别

进程🌴: 每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。进程之间的通信有操作系统传递,导致通讯效率低,切换开销大。

线程🌴: 一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。(PS:共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁"。一个线程在访问内存空间的时候,其他线程不允许访问,必须等待之前的线程访问结束,才能使用这个内存空间)


多进程和多线程

共同点:

👉🏻表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成。

区别:

对比维度 多进程 多线程
数据共享、同步 数据共享复杂,需要用IPC;数据是分开的,同步简单 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂
内存、CPU 占用内存多,切换复杂,CPU利用率低 占用内存少,切换简单,CPU利用率高
创建销毁、切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度很快
编程、调试 编程简单,调试简单 编程复杂,调试复杂
可靠性 进程间不会互相影响 一个线程挂掉将导致整个进程挂掉


通信方式


进程间的通信方式

  1. 消息队列📍: 消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
  2. 套接字📍: 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
  3. 信号量📍: 信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
  4. 共享内存📍: 使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。


线程间的通信方式(Java)

  1. volatile:保证了线程之间的可见性和有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
  2. 等待/通知机制:wait(),notify()和notifyAll()
  3. threadLocal 参见我的文章:《彻底明白ThreadLocal》


应用场景

多进程应用场景

  • nginx主流的工作模式是多进程模式(也支持多线程模型)
  • 几乎所有的web server服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached,httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程;
  • chrome浏览器也是多进程方式。 (原因: ①可能存在一些网页不符合编程规范,容易崩溃,采用多进程一个网页崩溃不会影响其他网页;而采用多线程会。②网页之间互相隔离,保证安全,不必担心某个网页中的恶意代码会取得存放在其他网页中的敏感信息。)
  • redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)


多线程应用场景

  • 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时)。
  • 提供非均质的服务(有优先级任务处理)事件响应有优先级。
  • 单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延。
  • 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)
  • 案例:

桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程;

如何选?

  1. 需要频繁创建销毁的优先用线程(进程的创建和销毁开销过大)
    这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的。
  2. 需要进行大量计算的优先使用线程(CPU频繁切换)
    所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
  3. 都满足需求的情况下,用你最熟悉、最拿手的方式
    至于“数据共享、同步”、“ 编程 、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,只能说:没有明确的选择方法。选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。

🙇实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。



拓展知识

进程间的调度算法

  1. 先到先服务 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  2. 短作业优先 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  3. 时间片轮转 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
  4. 优先级调度 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
  5. 多级反馈队列 前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。


总结

比喻⭐:如果说进程是工厂,那么线程就是工厂里的工人,而工人是要干活的。工厂(进程)给工人(线程)提供了内存空间,让工人(线程)干活。

进程是资源分配的基本单位,而线程是CPU调度的基本单位。因此,某种意义上可以说在支持线程的操作系统中没有真正意义上的进程调度,而都是线程调度。线程的出现主要是为了弥补进程各种操作开销大的问题,除这点外,线程和进程是差不多的。多线程和多进程一样,一方面为了充分利用CPU资源,另一方面是为了优化用户交互体验。

🚀🚀🚀

目录
相关文章
|
14天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
18天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
15天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
24 1
|
21天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
36 2
|
22天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
1月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
1月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
19 2
|
1月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
30 2
|
1月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
34 1
|
1月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
38 1

相关实验场景

更多
下一篇
无影云桌面