goroutine 相关知识9

go runtime M:N模型可以根据具体的操作类型(操作系统阻塞或非阻塞操作)调整goroutine和OS Thread的映射情况,更加灵活

两个M,即两个OS Thread线程,分别对应一个P,每一个P有负责调度多个G。组成的goroutine运行时的基本结构

G最重要的三个状态

  • Grunnable

  • Grunning

  • Gwaiting

状态迁移 Grunnable -> Grunning -> Gwaiting -> Grunnable

goroutine在状态发生转变时,会对栈的上下文进行保存和恢复

G中的Gobuf的定义

struct Gobuf
{
 uintptr sp; // 栈指针
 uintptr pc; // 程序计数器PC
 G* g; // 关联的G
};

保存栈上下文时,最重要的就是保存这个Gobuf结构中的内容

具体是通过 void gosave(Gobuf*) 以及 void gogo(Gobuf*) 实现栈上下文的保存和恢复

底层实现为汇编,因此goroutine的context swtich非常快


goroutine scheduler在几个主要场景下的调度策略

goroutine将scheduler的执行交给具体的M,即OS Thread

每一个M就执行一个函数,即 void schedule(void)

这个函数具体做得事情就是从各个运行队列中选择合适的goroutine然后执行goroutine中对应的 func

// 调度的一个回合:找到可以运行的G,执行
// 从不返回
static void schedule(void)
{
 G *gp;
 uint32 tick;

top:
 gp = nil;
// 时不时检查全局的可运行队列,确保公平性
// 否则两个goroutine不断地互相重生,完全占用本地的可运行队列
 tick = m->p->schedtick;
// 优化技巧,其实就是tick%61 == 0
if(tick - (((uint64)tick\*0x4325c53fu)>>36)\*61==0&& runtime·sched.runqsize >0) {
 runtime·lock(&runtime·sched);
 gp = globrunqget(m->p, 1);// 从全局可运行队列获得可用的G
 runtime·unlock(&runtime·sched);
if(gp)
 resetspinning();
 }
if(gp == nil) {
 gp = runqget(m->p); // 如果全局队列里没找到,就在P的本地可运行队列里找
if(gp && m->spinning)
 runtime·throw("schedule: spinning with local work");
 }
if(gp == nil) {
 gp = findrunnable(); // 阻塞住,直到找到可用的G
 resetspinning();
 }

// 是否启用指定某M来执行该G
if(gp->lockedm) {
// 把P给指定的m,然后阻塞等新的P
 startlockedm(gp); 
goto top;
 }

 execute(gp); // 执行G
}

几个问题:

  1. 当M发现分配给自己的goroutine链表已经执行完毕时怎么办?
  2. 当goroutine陷入系统调用阻塞后,M是否也一起阻塞?
  3. 当某个gorouine长时间占用CPU怎么办?

  • 从其他的P中偷取goroutine然后执行,略就是工作密取的机制

  • 当其他的P也没有可执行的goroutine时,从全局等待队列中寻找runnable的goroutine进行执行

  • 如果还找不到,则M让出CPU调度

例如阻塞IO读取本地文件,此时调用会systemcall会陷入内核,不可避免地会使得调用线程阻塞

goroutine将所有可能阻塞的系统调用均封装为gorouine友好的接口

具体,在每次进行系统调用之前,从一个线程池从获取一个OS Thread并执行该系统调用,而本来运行的gorouine则将自己的状态改为Gwaiting,并将控制权交给scheduler继续调度,系统调用的返回通过channel进行同步即可

因此其实goroutine也没有办法做到完全的协程化,因为系统调用总会阻塞线程

go支持简单的抢占式调度,sysmon线程检测goruntime的各种状态,长时间占用CPU的goroutine,如果发现了就将其抢占过来


goroutine 中最重要的一个设计就在于它将所有的语言层次上的api都限制在了goroutine这一层,进而屏蔽了执行代码与具体线程交互的机会

实际上就可以忽略线程的存在,把goroutine当成是一个非常廉价能够大量创建的Thread


JVM系语言(如scala,clojure),本质上都无法完全实现goroutine(clojure虽然有async,但是依然无法和JDK中的阻塞api结合良好)

假设在Java中基于Thread之上实现一个scheduler,一个轻量级的协程以及协程相关的原语(如resume, pause等),我们也只能基于我们自己封装的api来协助协程调度

如果在创建的协程中直接使用Java阻塞api,结果就是使用来调度协程的OS Thread陷入阻塞,无法继续运行scheduler进行调度

转载于:https://my.oschina.net/zhangthe9/blog/3022938

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值