python面试题 :进程、线程、协程的区别
进程、线程和协程区别
进程 线程 协程 区别对比表格总结
进程 | 线程 | 协程 | |
切换者 | 操作系统 | 操作系统 | 用户(编程者/应用程序),轻量级不受内核调度 |
切换时机 | 操作系统切换策略,用户不感知 | 操作系统切换策略,用户不感知 | 用户自己(的程序)决定 |
切换内容 |
页全局目录 内核栈 硬件上下文 |
内核栈 硬件上下文 |
硬件上下文 |
切换内容保存 | 内核栈 | 内核栈 | 用户自己的变量 用户栈或者堆 |
切换过程 | 用户态-内核态-用户态 | 用户态-内核态-用户态 | 用户态 |
切换操作 | 操作最终在内核层完成,应用层需要调用内核层提供的 syscall 底层函数 | 操作最终在内核层完成,应用层需要调用内核层提供的 syscall 底层函数 | 应用层使用代码进行简单的现场保存和恢复即可 |
上下文切换开销区别 |
每个进程有独立的代码和数据空间,进程之间上下文切换开销较大 |
线程组共享代码和数据空间,线程之间切换的开销较小 |
|
切换效率 |
低 |
中 |
高 |
调度者 |
资源调度的单位 资源分配和独立运行的最小单位 |
CPU调度的单位 任务调度和系统执行的最小单位 |
程序控制调度 |
任务调度 | 由内核实现,抢占方式,依赖各种锁 | 由内核实现,抢占方式,依赖各种锁 |
非抢占式,不需要锁机制 由用户态的实现的具体调度器进行。例如 go 协程的调度器 |
地址空间 |
【进程】 执行单位是 【线程】,一个进程内至少有一个线程, 进程有独立的地址空间 一个进程崩溃不影响其它进程 |
【线程】共享进程的空间 一个线程崩溃会导致整个进程崩溃 |
协程利用【intecepter执行器】关联线程池,来使用线程 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态 |
运行机制 | 同步机制 | 同步机制 | 异步机制 |
语言支持程度 | 大多数编程语言 | 大多数编程语言 | 部分语言:Lua,Go,Python ... |
包含关系 | 一个进程也可以单独拥有多个协程 |
一个线程可以有多个协程 每一个独立的线程有一个程序运行的入口,顺序执行序列和程序出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程控制 |
|
场景不同 | 线程组的所有线程共用一个进程的内存空间,安全稳定性相对较差,若其中一个线程发生崩溃,可能会使整个进程,因此对安全稳定性要求较高时,需要优先选择进程进行高并发编程 | 线程创建和销毁的代价低、上下文切换速度快、对系统资源占用小、对CPU的使用效率高,因此一般情况下优先选择线程进行高并发编程; | 协程拥有自己的寄存器上下文和栈,没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快
协程能保留上一次调用时的状态 |
1. 进程
进程是系统资源分配的最小单位, 系统由一个个进程(程序)组成一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
- 文本区域存储处理器执行的代码
- 数据区域存储变量和进程执行期间使用的动态分配的内存;
- 堆栈区域存储着活动过程调用的指令和本地变量。
因此进程的创建和销毁都是相对于系统资源,所以是一种比较昂贵的操作。 进程有三个状态:
- 等待态:等待某个事件的完成;
- 就绪态:等待系统分配处理器以便运行;
- 运行态:占有处理器正在运行。
进程是抢占式的争夺CPU运行自身,而CPU单核的情况下同一时间只能执行一个进程的代码,但是多进程的实现则是通过CPU飞快的切换不同进程,因此使得看上去就像是多个进程在同时进行.
通信问题: 由于进程间是隔离的,各自拥有自己的内存内存资源, 因此相对于线程比较安全, 所以不同进程之间的数据只能通过 IPC(Inter-Process Communication) 进行通信共享.
2. 线程
进程与线程其实最核心的是隔离与并行。
- 线程属于进程
- 线程共享进程的内存地址空间
- 线程几乎不占有系统资源
通信问题: 进程相当于一个容器,而线程而是运行在容器里面的,因此对于容器内的东西,线程是共同享有的,因此线程间的通信可以直接通过全局变量进行通信,但是由此带来的例如多个线程读写同一个地址变量的时候则将带来不可预期的后果,因此这时候引入了各种锁的作用,例如互斥锁等。
同时多线程是不安全的,当一个线程崩溃了,会导致整个进程也崩溃了,即其他线程也挂了,但多进程而不会,一个进程挂了,另一个进程依然照样运行。
线程能更好的使用cpu资源,即使是单核也能加大进程抢到cpu的机会,并且一个进程中的多个线程可以被多个处理器并行执行。
- 进程是系统分配资源的最小单位
- 线程是CPU调度的最小单位
- 由于默认进程内只有一个线程,所以多核CPU处理多进程就像是一个进程一个核心
3. 进程与线程对比
3.1 线程和进程的上下文切换
进程切换分3步:
- 切换页目录以使用新的地址空间
- 切换内核栈
- 切换硬件上下文
而线程切换只需要第2、3步,因此进程的切换代价比较大
3.2 其他方面
- 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
- 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
- 线程是处理器调度的基本单位,但进程不是
- 二者均可并发执行
- 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
4. 协程
对于进程、线程,都是有内核进行调度,有CPU时间片的概念,进行抢占式调度(有多种调度算法)。
对于协程(用户级线程),这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户的程序自己调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的CPU控制权切换到其他进程/线程,通常只能进行协作式调度,需要协程自己主动把控制权转让出去之后,其他协程才能被执行到。
- 协程是属于线程的。协程程序是在线程里面跑的,因此协程又称微线程和纤程等
- 协没有线程的上下文切换消耗。协程的调度切换是用户(程序员)手动切换的,因此更加灵活,因此又叫用户空间线程.
- 原子操作性。由于协程是用户调度的,所以不会出现执行一半的代码片段被强制中断了,因此无需原子操作锁。
5. 线程与协程对比
- 一个线程可以多个协程,即一个内核线程对应多个用户协程(用户进程)。
- 进程、线程,都是有内核进行调度,有CPU时间片的概念,进行抢占式调度(有多种调度算法)
- 协程的调度与内核无关,完全有程序进行控制。只能进行非抢占式调度。
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
- 极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
比较的点 | 线程 | 协程 |
---|---|---|
数据存储 | 内核态的内存空间 | 一般是线程提供的用户态内存空间 |
切换操作 | 操作最终在内核层完成,应用层需要调用内核层提供的 syscall 底层函数 | 应用层使用代码进行简单的现场保存和恢复即可 |
任务调度 | 由内核实现,抢占方式,依赖各种锁 | 由用户态的实现的具体调度器进行。例如 go 协程的调度器 |
语音支持程度 | 绝大部分编程语言 | 部分语言:Lua,Go,Python ... |
实现规范 | 按照现代操作系统规范实现 | 无统一规范。在应用层由开发者实现,高度自定义,比如只支持单线程的线程。不同的调度策略,等等 |
5.1. goroutine
本质上,goroutine 就是协程。 不同的是,Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU (P) 转让出去,让其他 goroutine 能被调度并执行,也就是 Golang 从语言层面支持了协程。
- 内存消耗方面
每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少。 goroutine: 2KB 线程: 8MB
- 线程/goroutine 切换(调度)开销方面
线程/goroutine 切换开销方面,goroutine 远比线程小 线程: 涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP...等寄存器的刷新等。 goroutine: 只有三个寄存器的值修改 - PC / SP / DX.
6. 同步(Sync)和异步(Async)
6.1 同步:
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。
简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
例如:B/S模式中的表单提交,具体过程是:客户端提交请求->等待服务器处理->处理完毕返回,在这个过程中客户端(浏览器)不能做其他事。
6.2 异步:
异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
对于通知调用者的三种方式,具体如下:
状态
即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。
通知
当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。
回调
与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。
例如:B/S模式中的ajax请求,具体过程是:客户端发出ajax请求->服务端处理->处理完毕执行客户端回调,在客户端(浏览器)发出请求后,仍然可以做其他的事。
同步和异步的区别:
总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
问题
1. 协程之间是同步还是异步,对于同一个线程而言,里面的不同的协程是同步还是异步
同步还是异步是要根据具体情况来分析
参考链接:
https://segmentfault.com/q/1010000004878639
https://blog.csdn.net/daaikuaichuan/article/details/82951084?utm_source=distribute.pc_relevant.none-task
https://juejin.im/post/5b0014b7518825426e023666
https://github.com/iostalks/Blog/issues/1
https://blog.csdn.net/u013007900/article/details/89016375
https://www.jianshu.com/p/dd4a480a1410
http://www.sizeofvoid.net/goroutine-under-the-hood/
https://blog.csdn.net/qq_41853758/article/details/83514635