1.理解协程是如何被调度的
java中的线程和系统线程的调度关系为1:1
go中协程和系统线程的调度关系为M:N,这要得益于他的MPG调度模型

- M,即系统线程
- P,协程处理器,可以承载若干G,使i这些G适时与M进行对接,相当于中介
- G,协程,由go关键字创建。G存放于队列中,分为全局的G队列和每个P自己维护的G队列
因此由于P的存在,当一个正在与某个M对接并运行的G,需要因某个事件(如IO)而暂停运行时,P总是能及时发现并把G与M分离,以释放资源供等待运行的G使用,以此来实现多对多的关系
而当一个 G 需要恢复运行的时候,调度器又会尽快地为它寻找空闲的计算资源(包括 M)并安排运行。另外,当 M 不够用时,调度器会帮我们向操作系统申请新的系统级线程,而当某个 M 已无用时,调度器又会负责把它及时地销毁掉。
正因为调度器帮助我们做了很多事,所以我们的 Go 程序才总是能高效地利用操作系统和计算机资源。程序中的所有 goroutine 也都会被充分地调度,其中的代码也都会被并发地运行,即使这样的 goroutine 有数以十万计,也仍然可以如此

go函数什么时候被执行?
demo
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
wg.Add(1)
fmt.Println(i)
}(i)
}
wg.Wait()
}
go 函数真正被执行的事件,总会与其所属的go语句被执行的事件不同,当程序执行到一条go语句,会先视图从某个存放空闲的G的队列中获取一个G,它只有在找不到空闲G的情况下才会去创建一个新的G
在拿到一个空闲的G后,GO会视图用这个G去包装当前的那个go函数(即函数中的代码),然后再把这个G追加到某个存放可运行的G的队列中
正因为是队列,先入先出,因此go函数的执行时间总会滞后于它所属的go语句的执行时间
并且go语句只关心本身执行完毕,GO程序不会去等待go函数执行,因此若不加waitGroup,我们将看不到任何的输出(因为程序已经执行完了,for语句执行速度很快,当它执行完毕,不添加机制的话,10个包装的协程往往没有得到机会运行)
网友评论