进程和线程
进程
教科书上解释:进程是资源分配的最小单位
👀可以理解为:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文。进程的生命周期有调入,执行,保存的过程。
线程
教科书上解释:线程是CPU调度的最小单位
👀可以理解为:一个应用程序的执行可能有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块;这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,单核任务中划分更为细小的CPU时间段。
进程和线程的区别
进程🌴: 每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。进程之间的通信有操作系统传递,导致通讯效率低,切换开销大。
线程🌴: 一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。(PS:共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁"。一个线程在访问内存空间的时候,其他线程不允许访问,必须等待之前的线程访问结束,才能使用这个内存空间)
多进程和多线程
共同点:
👉🏻表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成。
区别:
对比维度 | 多进程 | 多线程 |
---|---|---|
数据共享、同步 | 数据共享复杂,需要用IPC;数据是分开的,同步简单 | 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 |
内存、CPU | 占用内存多,切换复杂,CPU利用率低 | 占用内存少,切换简单,CPU利用率高 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度很快 |
编程、调试 | 编程简单,调试简单 | 编程复杂,调试复杂 |
可靠性 | 进程间不会互相影响 | 一个线程挂掉将导致整个进程挂掉 |
通信方式
进程间的通信方式
- 消息队列📍: 消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
- 套接字📍: 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
- 信号量📍: 信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
- 共享内存📍: 使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
线程间的通信方式(Java)
- volatile:保证了线程之间的可见性和有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
- 等待/通知机制:wait(),notify()和notifyAll()
- threadLocal 参见我的文章:《彻底明白ThreadLocal》
应用场景
多进程应用场景
- nginx主流的工作模式是多进程模式(也支持多线程模型)
- 几乎所有的web server服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached,httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程;
- chrome浏览器也是多进程方式。 (原因: ①可能存在一些网页不符合编程规范,容易崩溃,采用多进程一个网页崩溃不会影响其他网页;而采用多线程会。②网页之间互相隔离,保证安全,不必担心某个网页中的恶意代码会取得存放在其他网页中的敏感信息。)
- redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)
多线程应用场景
- 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时)。
- 提供非均质的服务(有优先级任务处理)事件响应有优先级。
- 单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延。
- 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)
- 案例:
桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程;
如何选?
- 需要频繁创建销毁的优先用线程(进程的创建和销毁开销过大)
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的。 - 需要进行大量计算的优先使用线程(CPU频繁切换)
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。 - 都满足需求的情况下,用你最熟悉、最拿手的方式
至于“数据共享、同步”、“ 编程 、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,只能说:没有明确的选择方法。选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。
🙇实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。
拓展知识
进程间的调度算法
- 先到先服务 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 短作业优先 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 时间片轮转 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
- 优先级调度 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
- 多级反馈队列 前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
总结
比喻⭐:如果说进程是工厂,那么线程就是工厂里的工人,而工人是要干活的。工厂(进程)给工人(线程)提供了内存空间,让工人(线程)干活。
进程是资源分配的基本单位,而线程是CPU调度的基本单位。因此,某种意义上可以说在支持线程的操作系统中没有真正意义上的进程调度,而都是线程调度。线程的出现主要是为了弥补进程各种操作开销大的问题,除这点外,线程和进程是差不多的。多线程和多进程一样,一方面为了充分利用CPU资源,另一方面是为了优化用户交互体验。
🚀🚀🚀