面试题答案
一键面试Go调度器采用的调度策略
- M:N调度模型
- Go调度器采用M:N调度模型,即多个用户级线程(Goroutine)映射到多个内核级线程(M)上。这使得Go可以在少量的内核线程上高效运行大量的Goroutine,避免了传统1:1模型中创建大量内核线程带来的资源开销。
- G - P - M模型
- G(Goroutine):代表用户级的协程,是Go语言中轻量级的执行单元。
- P(Processor):处理器,它包含了运行Goroutine的资源,如Goroutine队列。每个P都有一个本地的Goroutine队列,同时也可以从全局Goroutine队列中获取Goroutine。P的数量一般由GOMAXPROCS环境变量决定,默认等于CPU核心数。
- M(Machine):内核级线程,负责执行Goroutine。M需要绑定到一个P上才能运行Goroutine。
- 抢占式调度
- Go 1.14版本引入了协作式抢占的增强版——抢占式调度。在协作式调度中,Goroutine需要主动让出CPU,例如通过调用系统调用、channel操作等。而抢占式调度允许在Goroutine执行时,由调度器在合适的时机(如函数调用、循环等边界)强制暂停Goroutine,将CPU资源分配给其他Goroutine,从而避免某个Goroutine长时间占用CPU。
不同场景下策略的协同工作及性能提升
- 高并发I/O场景
- 假设一个Web服务器程序,有大量的HTTP请求处理Goroutine。当某个Goroutine进行I/O操作(如读取HTTP请求数据、写入响应数据等)时,会陷入阻塞。
- 基于M:N调度模型,该Goroutine不会阻塞对应的M线程。M线程会解绑当前P,去寻找其他可运行的Goroutine(从P的本地队列或全局队列中获取),从而充分利用CPU资源。
- 例如,在处理一个请求时,Goroutine需要从数据库读取数据,此时它会阻塞。调度器会将M线程重新分配给其他等待执行的Goroutine,如处理新的HTTP请求,提高了服务器的整体并发处理能力。
- CPU密集型场景
- 对于一些计算密集型的任务,如科学计算、加密运算等。如果没有抢占式调度,一个CPU密集型的Goroutine可能会长时间占用CPU,导致其他Goroutine无法执行。
- 有了抢占式调度后,调度器会在合适的时机(如函数调用边界)暂停当前CPU密集型的Goroutine,将CPU资源分配给其他Goroutine。例如,一个计算素数的Goroutine在执行一个复杂的循环计算时,调度器会在循环中的函数调用处暂停它,让其他Goroutine有机会运行,从而提高了程序整体的响应性和资源利用率。
- 任务均衡场景
- 在P的本地队列中,Goroutine是按照FIFO(先进先出)的顺序执行的。当一个P的本地队列中的Goroutine执行完后,M线程会尝试从全局队列中获取Goroutine。
- 同时,当一个P的本地队列中有太多Goroutine时,会进行“偷取”操作。例如,某个P上的任务特别繁重,其他空闲的P对应的M线程可以从这个繁忙的P的本地队列中“偷取”一半的Goroutine到自己的本地队列中执行,从而实现任务在各个P之间的均衡分配,提高整体性能。