一、介绍
goroutine 是 Go语言中的轻量级线程,用户态级别,由 Go 运行时(runtime)调度和管理,它比线程更加易用、高效和轻便
每一个并发的执行单元叫作一个goroutine,Go 程序会自动为 main() 函数创建一个默认的goroutine
1 goroutine和线程的区别
1) 内存消耗
创建一个goroutine的栈内存消耗为 2 KB,如果栈空间不够用,会自动进行扩容
创建一个线程需要消耗 1 MB 栈内存,而且还需要一个被称为 "a guard page" 的区域用于和其他线程的栈空间进行隔离
2) 创建和销毀
线程创建需要向操作系统申请资源,销毀时将资源归还,因此创建和销毀线程的资源消耗比较大,属于内核级
goroutine的创建和销毁是由 Go runtime 负责管理的,消耗非常小,属于用户级
3) 切换
线程的调度方式是抢占式的,切换时需要保存各种寄存器,以便将来恢复。线程切换会消耗 1000-1500 ns
goroutine的调度方式是协同式的,切换时只需保存三个寄存器: Program Counter, Stack Pointer and BP。goroutine 切换会消耗 200 ns
2 创建goroutine
使用 go 关键字创建一个goroutine,一个函数可以被创建多个goroutine,一个goroutine必定对应一个函数
每个groutine中的返回值都会被忽略,如果需要从goroutine中返回数据,请使用通道(channel)把数据从goroutine中返回
格式
go 函数名(参数列表)
二、Golang的GMP模型
1 GMP模型
G(Goroutine): go协程
M(Machine): 工作线程或内核线程,G 需要调度到 M 上才能运行
P(Processor): 调度器,负责调度goroutine,保存了当前goroutine运行的上下文(函数指针,堆栈地址及地址边界),同时还负责部分内存的管理。维护一个本地goroutine队列,M从P上获得goroutine并执行
其中:
M 与 P 的数量没有绝对关系
P在程序启动时创建,个数在程序启动时决定,由环境变量GOMAXPROCS的值或程序运行runtime.GOMAXPROCS()进行设置,默认情况下等同于CPU的核数
没有足够的M来关联P并运行其中的可运行的G时创建M
G需要在M上才能运行,M通过P的调度获取G,在某一时刻,一个M上只有一个G在运行(g0除外)
P拥有一个G队列,里面是已经就绪的G,是可以被调度到线程栈上执行的协程,称为运行队列
M堵塞,P会创建或者切换一个新的M
2 关系图
![](/i/l/?n=22&i=blog/1924307/202208/1924307-20220824165021698-1923852692.png)
其中:
goroutine队列中: 黄色的G表示正在执行中的goroutine,灰色的G表示等待调度的goroutine
3 调度策略 1) 从工作线程M的本地运行队列中寻找goroutine 2) 从全局运行队列中寻找goroutine,并不是每次调度都会从全局队列获取可运行的 goroutine。每个工作线程每经过61次调度就需要优先尝试从全局运行队列中找出一个goroutine来运行,这样做的原因:保证调度的公平性,保证位于全局运行队列中的goroutine得到调度的机会; 全局运行队列是所有工作线程都可以访问的,从全局获取需要上锁,开销大 3) 从其它工作线程的运行队列中偷取goroutine。如果经过上述策略还没有找到需要运行的goroutine,则从其他工作线程的运行队列中偷取goroutine,在偷取之前会再次尝试从全局运行队列和当前线程的本地运行队列中查找需要运行的goroutine,并且检测一下其他所有的 P 是否都处于空闲状态,如果是,则说明其他 P没有寻找到goroutine,进入休眠状态 4) 经过上述过程还是找不到goroutine,则进入休眠状态,在进入休眠状态之前还是会到全局运行队列中寻找goroutine,如果没有正式进入休眠状态,等待被其他 M 唤醒 标签:队列,创建,goroutine,调度,线程,运行 From: https://www.cnblogs.com/gudanaimei/p/16620037.html