作者回复: ✅
作者回复: 1.按照Dmitry Vyukov原文的意思: 集中状态(centralized state),我理解就是一把全局锁要保护的数据太多。这样无论访问哪个数据,都要锁这把全局锁。数据局部性差是因为每个m都会缓存它执行的代码或数据,但是如果在m之间频繁传递goroutine,那么这种局部缓存的意义就没有了。无法实现局部缓存带来的性能提升。 2. runtime.morestack_noctxt 是一个函数。 3. 协作式:大家都按事先定义好的规则来,比如:一个goroutine执行完后,退出,让出p,然后下一个goroutine被调度到p上运行。这样做的缺点就在于 是否让出p的决定权在groutine自身。一旦某个g不主动让出p或执行时间较长,那么后面的goroutine只能等着,没有方法让前者让出p,导致延迟甚至饿死。而非协作: 就是由runtime来决定一个goroutine运行多长时间,如果你不主动让出,对不起,我有手段可以抢占你,把你踢出去,让后面的goroutine进来运行。 4. 挂起这里你可以理解为暂停运行,等待下一次调度。 5. 当进行一些慢系统调用的时候,比如常规文件io,执行系统调用的m就要和g一起挂起,这是os的要求,不是go runtime的要求。毕竟真正执行代码的还是m。
作者回复: 好问题!不过当网络I/O阻塞时,M真不会阻塞。 因为runtime层实现了netpoller,netpoller是基于os提供的I/O多路复用模型实现的(比如:linux上的epoll),这允许一个线程处理多个socket。这就使得当某个goroutine的socket发送i/o阻塞时,仅会让goroutine变为阻塞状态,runtime会将对应的socket与goroutine加入到netpoller的监听中,然后M继续执行其他goroutine的任务。等netpoller监视到之前的socket可读/可写时,再把对应的goroutine唤醒继续执行网络i/o。
作者回复: 👍
作者回复: 欧长坤老师维护的这个go history中有关调度器的演化可以参考:https://golang.design/history/#scheduler
作者回复: 对,goroutine退出后,runtime层的G对象会被放入一个Free G对象的池子中,后续有新Goroutine创建时,优先从池子里get一个。可以看Go源码:runtime/proc.go中的代码。
作者回复: 由于go运行时netpoller的存在,我们很难精确区分。这种“网络io”会被runtime转换为goroutine的调度等,不一定真的实施网络io。
作者回复: 《Go语言第一课FAQ》中有这方面的解答,你可以先看看。 https://tonybai.com/go-course-faq
作者回复: 👍
作者回复: 试试将P的数量置为1.