面试题答案
一键面试Go调度器工作原理
- M:N调度模型:
- Goroutine(G):是Go语言中轻量级的执行单元,由Go运行时管理。它非常轻量,创建和销毁的开销很小。
- M(Machine):代表操作系统线程。一个M对应一个操作系统线程,它负责执行G。
- P(Processor):处理器,它维护了一个本地的G队列,并且关联一个M。P的数量默认等于CPU的核心数,可以通过
runtime.GOMAXPROCS
函数进行设置。 - 工作流程:G被创建后,会被放入某个P的本地队列。M从P的本地队列中获取G并执行。如果P的本地队列为空,M会尝试从其他P的本地队列中偷取一半的G(work - stealing机制)。如果所有P的本地队列都为空,M会从全局G队列中获取G。
- 高效管理大量goroutine的方式:
- 轻量级线程:Goroutine的实现非常轻量,相比操作系统线程,其创建、销毁和切换的开销都极小,这使得可以轻松创建大量的goroutine。
- 工作窃取算法:当某个M执行完自己P本地队列中的G后,会从其他P的本地队列中偷取G,这样可以有效避免某个P负载过重,而其他P空闲的情况,充分利用了系统资源。
- 非抢占式调度:默认情况下,Goroutine采用协作式调度,即当一个G执行了如I/O操作、系统调用或主动调用
runtime.Gosched()
等函数时,会让出CPU,让其他G有机会执行。这减少了调度的复杂性和开销。
多核CPU环境下的优势
- 充分利用多核资源:通过设置
runtime.GOMAXPROCS
为CPU核心数,每个P可以绑定到一个CPU核心,不同的M在不同的P上并行执行不同的G,从而充分利用多核CPU的计算能力,提高程序的并行处理能力。 - 高并发处理能力:由于Goroutine的轻量级特性和高效的调度算法,在多核CPU环境下可以轻松支持大量的并发任务,提高系统的整体吞吐量。
多核CPU环境下的挑战
- 非抢占式调度的局限:在多核环境下,如果某个G长时间占用CPU且不主动让出(如执行密集型计算任务且未调用协作调度函数),会导致其他G无法及时执行,不能充分发挥多核的优势。Go 1.20引入了基于信号的抢占式调度来缓解这个问题,但仍存在一些场景下的局限性。
- 资源竞争:虽然工作窃取算法能在一定程度上均衡负载,但在某些极端情况下,可能仍然会出现资源竞争问题,例如多个M同时尝试从同一个全局G队列中获取G,或者同时从其他P的本地队列中偷取G,可能会导致性能下降。
- 数据一致性:在多核并行执行多个Goroutine时,由于它们可能同时访问和修改共享数据,需要额外的同步机制(如互斥锁、读写锁等)来保证数据的一致性,这增加了编程的复杂性。