面试题答案
一键面试G、P、M各自职责
- G(Goroutine):
- 代表一个轻量级的执行单元,也就是Go语言中的协程。它包含了运行的函数以及相关的栈空间等执行状态信息。每个Goroutine都可以看作是一个独立的逻辑线程,但是其创建和销毁的开销远远小于操作系统线程。
- P(Processor):
- 处理器,它管理着一个本地的Goroutine队列。P的主要职责是提供一个执行环境,用于执行Goroutine。它维护了运行Goroutine所需的资源,如栈空间等。同时,P与操作系统的线程(M)进行绑定,决定了哪些Goroutine可以在哪个M上运行。每个P有一个本地的G队列,新创建的G会优先放入本地队列,如果本地队列满了则放入全局队列。
- M(Machine):
- 对应操作系统线程,它负责执行Goroutine。M从P的本地G队列或者全局G队列中获取Goroutine并执行。M需要与P绑定才能运行Goroutine,当M执行一个系统调用时,它会与P解绑,P可以被其他空闲的M复用,而该M会等待系统调用完成后重新获取一个P来继续执行Goroutine。
调度具体流程
- 创建Goroutine:
- 当使用
go
关键字创建一个新的Goroutine时,它会被优先放入创建它的P的本地G队列中。如果本地队列已满,就会被放入全局G队列。
- 当使用
- M获取Goroutine:
- 每个M会尝试从与之绑定的P的本地G队列中获取Goroutine来执行。如果本地队列为空,M会尝试从全局G队列中获取一批(通常为1/64的全局G数量)Goroutine并放入本地队列。如果全局队列也为空,M会尝试从其他P的本地队列中窃取一半的Goroutine(工作窃取算法)。
- 执行Goroutine:
- M从P获取到Goroutine后,开始执行其包含的函数。在执行过程中,如果Goroutine执行一个系统调用(如I/O操作),M会与P解绑,此时P可以被其他空闲的M复用。而该M会等待系统调用完成,完成后重新获取一个P继续执行Goroutine。
- Goroutine结束:
- 当Goroutine执行完其函数逻辑,它会从执行的M上退出,M会继续从P的本地队列中获取新的Goroutine执行,如果本地队列为空则重复上述获取Goroutine的流程。
多核CPU环境下高效并发调度
- 多P并行:
- 在多核CPU环境下,可以通过设置多个P来充分利用多核资源。Go运行时会根据CPU核心数自动设置合适数量的P(默认为CPU核心数)。每个P可以绑定到不同的M上,这样不同的M就可以同时执行不同P中的Goroutine,实现真正的并行执行。
- 负载均衡:
- 工作窃取算法在多核环境下发挥重要作用。当某个P的本地G队列繁忙,而其他P的本地G队列空闲时,繁忙的P对应的M可以从空闲P的本地队列中窃取Goroutine,从而实现负载均衡,提高整体的并发效率。
- 减少锁争用:
- GPM模型通过将Goroutine的管理分散到多个P上,减少了全局资源的锁争用。每个P管理自己的本地G队列,只有在操作全局G队列等共享资源时才需要加锁,这样在多核环境下可以有效减少锁争用带来的性能损耗,提升并发调度的效率。