优点
1.goroutine内存占用小,一个goroutine的内存占用只有2kb,而一个线程的占用内存约1M。
2.通常线程直接作用于cpu上,和操作系统打交道,属于内核级;而goroutine由go的调度器管理,属于用户级的。
3.goroutines上下文切换成本小,只有线程的五分之一。
4.goroutine是轻量级线程,go可以同时开启上万个协程,且运行非常稳定,这就是go并发上的优势。
调度原理
G:goroutine P:处理器 M:thread线程
1.线程是最终运行goroutine的实体,处理器的作用是将可运行的goroutine分配到工作线程上
2.线程如果想要运行goroutine需要先获取P,从P的本地队列中获取可运行的goroutine
3.M运行goroutine,goroutine执行完,M会通过P获取下一个goroutine,不断重复下去
1.P维系着本地队列和全局队列两种队列,全局队列和本地队列都存放着待运行的goroutine,本地队列存放数量不超过256个
2.新建的goroutine会先存放到P的本地队列中,如果本地队列已满,则会将本地队列的前一半拿出来和新建的goroutine一同放到全局队列中
3.本地队列为空时,M会从全局队列中获取一批g(p的数量影响获取g的数量),当全局队列为空,M就会从其他P的本地队列的尾部偷一半的g放到自己的队列中。
m执行g是一个循环机制,m在固定时间片内执行g,执行完的g会将其销毁,未执行完的g会重新放回P的本地队列,本地队列已满会放入全局队列
细节
1.g在执行时如果新创建了一个g,新建的g会首先放在执行当前m绑定的p的本地队列中
2.g在新建g时,会尝试从m的休眠队列中唤醒新的m,当m被成功唤醒后会成为自旋线程,直到偷到goroutine恢复为普通线程
3.当M遇到G阻塞时,M会释放P,把P转移给其他空闲的M;如果没有空闲的M时,系统会创建M或让p进入空闲P队列,等待空闲的M(具体是创建M还是让P等待,由当前系统策略决定)
其他
M0:程序开始运行时候会先创建M0,即main线程,当main线程执行完自身的初始化操作并创建 G0 后就和其他线程没区别了
G0:每个M创建时都会新建一个G0,用于M的初始化和P的本地队列中的g的调度工作,G0会一直运行在M的全局变量中,G0伴随M的创建和销毁
自旋线程:P本地队列为空,不断尝试获取g的线程;自旋线程抢占g而不抢占P,因为自旋线程已经有绑定了的P
饥饿模式:当前m长时间无法获取可执行的g,会释放当前持有的P,开始等待一个新的P。确保线程执行的公平性和避免资源的长期独占。