前言
前置知识:
这一篇来讲解下Go中多线程是如何运行协程的,有什么问题需要解决。
单线程循环模型
Go中单线程执行协程的流程如下,业务方法块即是协程的代码,通过一个大循环不断的调用协程。
抽象为模型如下
- M代表线程,用三角形包裹
- G代表一个个协程,用圆包裹
即M线程不断的执行G协程,执行完后又从队列中获取新的协程并执行。
多线程循环模型
多线程则是从全局队列中获取协程,runnable queue为可用的全局协程队列。当然为了并发安全,协程队列需要有锁。
抽象为模型如下,每个线程都有一个正在运行的协程,运行完后互斥的获取全局队列中的协程继续执行。
这种模型的特点总结
- 由线程运行协程,无需操作系统调度切换。
- 线程运行一个调度循环,按顺序的执行协程。
- 协程队列类似资源池,线程获取协程时需要上锁。
这会带来什么问题
- 协程无法并发,只能按顺序执行。
- 多线程互斥的访问全局队列,影响性能,甚至使线程饥饿。
如何提高多线程性能
举例一个场景。
老师叫一批同学帮忙批改试卷,试卷统一放在老师那。如果每个人每次只拿一张试卷,改完后再去老师那拿,那就太浪费时间了。正常情况下,应该每个同学都拿一批试卷,改完后再要一批,批改效率大大提升。
同理,多线程应该各自有一个本地队列,每次从全局队列中获取一批协程放入本地队列中,处理完后再拿下一批协程。
于是模型就变成这样
而本地队列的获取则交由处理器P实现,如此GMP模型浮出水面。
总结
多线程模型互斥访问全局队列会带来的性能问题,为此,Go在多线程中引入了本地队列,通过批次获取协程来减少访问全局队列,减少竞争锁的时间开销。这里留下第一个问题,将在下一篇解析著名的GMP模型中解决。