GMP都是干什么的
一句话概括它们的定义
在本篇以及后续的文章中, 我们会大量介绍G/M/P三者之间的交互, 以及go里面各种现象的实现原理. 因此一定要先搞清楚这三者分别是什么
G
G, Goroutine, 代表用户协程. 你定义的所有函数都是在G里面运行的, 包括你定义的go func
, 甚至main
函数都是在G里面运行的.
但是G怎么运行呢? 协程本身不会运行任何东西, G把函数定义, 函数参数都包装包装, 然后提供一些栈, 包装成一个可以直接拿来运行的结构体, 这就是G的工作
P
P最为明显的一个特点就是P持有一个G队列. 每次我们设置GOMAXPROCS
就是在设置P的数量. 没错, P决定了真正并行的数量.
除此之外, P是一个对象复用中心, 里面包含了很多用完了等着下次接着用的结构体, 比如defer
结构体, 都是以P为维度的
M
M是一个真正的线程, 操作系统直接管辖的那种, 跟上面二者之间的关系为: 一个P配一个M, 持有一个队列的G, M会把P中队列里的G拿出来运行.
为什么需要P
如果说G是用户定义的Goroutine很好理解, M是系统线程也很好理解, 那么为什么我们一定需要P? P能解决什么问题?
一个最大的问题在于如果现在因为某些问题产生了阻塞(如系统调用), 这些阻塞会导致后面的G无法被执行. 但是我们有了P以后, 我们能做到在系统调用的时候及时解绑P与M, P会带着自己剩下的G另寻一个M来继续执行任务, 这个M可能是新创建的, 也可能是在缓存池里的
GMP模型下的并发安全问题
我们会大量介绍一些并发时的安全问题(如Map,Lock), 想要知道怎样保证安全就要知道, 什么样的两个G是"并行"的, 因为只有并行的G才可能会读取同一块数据.
如上图所示真正能产生争抢的, 只有不同M中正在运行的G才会产生争抢, 如果你的数据是以P为维度的, 也就是说只有本P才能取得, 这种是不会产生争抢的(如Pool里的私有数据区域). 这种情况下才需要上锁, 这是并发安全问题的基本,
后面我们会介绍的内容
G
G结构体到底包装了那些东西
G栈是什么样的? 如果G有栈, 那么栈是怎么扩容的?
P
什么叫"对象复用中心"?
P是怎么管理所持有的G的?
M
GMP三者是怎么调度并开始工作的?
为什么会有缓存池里的M这种概念?
Last updated
Was this helpful?