Go 多线程是如何运行协程的

633 阅读2分钟

前言

前置知识:

Go 了解协程

Go 深入考究协程

这一篇来讲解下Go中多线程是如何运行协程的,有什么问题需要解决。

单线程循环模型

Go中单线程执行协程的流程如下,业务方法块即是协程的代码,通过一个大循环不断的调用协程。

image.png

抽象为模型如下

  • M代表线程,用三角形包裹
  • G代表一个个协程,用圆包裹

即M线程不断的执行G协程,执行完后又从队列中获取新的协程并执行。

image.png

多线程循环模型

多线程则是从全局队列中获取协程,runnable queue为可用的全局协程队列。当然为了并发安全,协程队列需要有锁。

image.png

抽象为模型如下,每个线程都有一个正在运行的协程,运行完后互斥的获取全局队列中的协程继续执行。

image.png

这种模型的特点总结

  • 由线程运行协程,无需操作系统调度切换。
  • 线程运行一个调度循环,按顺序的执行协程。
  • 协程队列类似资源池,线程获取协程时需要上锁。

这会带来什么问题

  1. 协程无法并发,只能按顺序执行。
  2. 多线程互斥的访问全局队列,影响性能,甚至使线程饥饿。

如何提高多线程性能

举例一个场景。

老师叫一批同学帮忙批改试卷,试卷统一放在老师那。如果每个人每次只拿一张试卷,改完后再去老师那拿,那就太浪费时间了。正常情况下,应该每个同学都拿一批试卷,改完后再要一批,批改效率大大提升。

同理,多线程应该各自有一个本地队列,每次从全局队列中获取一批协程放入本地队列中,处理完后再拿下一批协程。

于是模型就变成这样

image.png

而本地队列的获取则交由处理器P实现,如此GMP模型浮出水面。

image.png

总结

多线程模型互斥访问全局队列会带来的性能问题,为此,Go在多线程中引入了本地队列,通过批次获取协程来减少访问全局队列,减少竞争锁的时间开销。这里留下第一个问题,将在下一篇解析著名的GMP模型中解决。