线程、协程、goroutine
线程
协程
用户级线程,从属于一个内核级线程。无法并行,从属于同一线程的一批协程中只要有一个阻塞,这个线程上所运行的协程将全部阻塞
goroutine
经过Golang优化过后的特殊“协程” 与线程存在映射关系
通过Go调度器来实现动态映射
- 与线程存在映射关系,M:N,是多对多模型,映射关系是动态调整的
- 协程的创建、调度、销毁发生在内核态,对内核透明
- goroutine在多对多模型下可以实现并行
- 栈空间大小可以动态调整
对比
gmp模型
goroutine + machine + processor
g
goroutine拥有自己的运行栈、状态、以及执行的任务函数(用户通过go func来指定)。g需要绑定到p才能执行,在g的视角中,p就是他的cpu
type g struct {
m *m
sched gobuf
}
type gobyf struct {
sp uintptr
pc uintptr
ret uintptr
bp uintptr // for framepointer-enabled architectures
}
- m:指向当前g的m
- sched.sp:保存cpu中的rsp寄存器中的值,指向函数调用栈的栈顶
- sched.pc:保存cpu中的rip寄存器中的值,指向程序下一条指令的地址
- sched.ret:保存系统调用的返回值
- sched.bp:保存cpu中的rbp寄存器的值,存储函数栈帧的起始位置
m
m即machine,是golang中对线程的抽象。线程不直接执行g,而是先和p绑定,由其实现代理。
type m struct {
g0 *g // goroutine with scheduling stack
tls
}
- g0:始祖协程一类特殊的调度协程,不用于执行g之间的切换调度,与m的关系是为1:1
p
p即processor,是golang中的调度器。因为processor的存在,goroutine和线程之间可以实现灵活映射。
type p struct {
// ...
runqhead uint32
runqtail uint32
runq [256]guintptr
runnext guintptr
// ...
}
- runq:本地goroutine队列,最大长度为256
- runqhead:队列头部
- runtail:队列尾部
- runnext:下一个可执行的goroutine
schedt
type schedt struct {
// ...
lock mutex
// ...
runq gQueue
runsize int32
// ...
}
sched是全局goroutine队列的封装
- lock:操作全局队列的锁
- runq:全局gorotine队列
- runqsize:全局goroutine队列的容量
gmp
如果某个p发现本地队列和全局队列都没有可以用的g,则会从其他p的本地队列中调度部分g来实现负载均衡。