美文网首页
golang MPG并发模型

golang MPG并发模型

作者: 突击手平头哥 | 来源:发表于2021-11-06 22:53 被阅读0次

    golang MPG并发模型

    mpg并发模型图.png

    以上这张图就是golangmpg模型中各个元素的说明:

    • M:物理线程,和其他语言中的线程是一致的;最大限制为10000个
    • P:逻辑处理器,负责调度协程;通常数量和CPU数量一致
    • G:即golang中通过go开启的协程

    协程和线程的区别

    协程被称作轻量级线程,在go语言中有几个优势:

    • 协程栈初始大小为2k,远小于线程的1M;且协程栈可以动态扩容,最大到1G
    • 协程的切片是逻辑控制器P在语言级别(用户空间)实现的,相比于系统级的线程切换消耗少很多

    协程的调度

    正常情况下P会从自身的空闲队列中取出一个G来执行,在早期版本中golang实现的是非抢占式调用,只有遇到IO、管道、runtime.Gosched()等阻塞操作时才会进行切换

    协程本身无法是无法自行进行切换的,在G遭遇到阻塞操作时,P会将当前的M脱离并同时绑定到一个新的线程M上,而原本的线程M则会继续阻塞在原本G的调用

    除了和P绑定的线程外,其他的线程主要是就是用来处理被阻塞的任务上的

    协程队列

    go语言中有一个全局协程队列,使用go开启的新协程就会被放入这个队列中、阻塞的M执行完毕后也是将G放入到这个全局,P会定期从这里拉取新的G

    而每个P又会自己维护一个G队列,在消费掉自身的G后会先从全局队列中拉取;如果没有的话就从其他P的队列中偷取,每次偷一半

    lua中的协程

    lua中的协程和go语言的协程完全时不一样的,lua所有代码运行在一个线程中,实际上并不是并发的;

    lua语言是不需要调度器P的,主要是协程内部主动调用函数切换,本质其实是类似于函数调用

    抢占式调用

    早期go语言实现的是非抢占式调用,这样的问题在于

    for{}
    

    如果只有一个P的情况下执行到上述代码,程序就会永远循环在这里,其他协程再也无法执行到

    更严重的问题是是,go语言的垃圾回收是需要停止整个世界的,如果某个协程永远不停止,那么垃圾回收就会一致等待

    但是如果是抢占式,那么就会在切换任务时,保存当前的上下文环境,因为当前线程如果正在做一件事,做到一半我们就强制停止,这时我们就必须多保存很多信息,避免再次切换回来时任务出错,这是需要付出代价的

    go语言实现的抢占式调用是非常初级的,而且最终还是需要协程主动让出才能切换

    什么时候需要抢占式调用

    • 执行时间过长的协程:防止其他协程饿死
    • GC需要停止某个协程来进行栈扫描
    • GC需要STW停止整个世界再进行工作

    sysmon

    在程序初始化的时候会创建一个后台线程执行sysmon,在程序执行期间每隔20us~10ms执行一次,对于执行超过10ms的协称会打上标记,供后续进行切换

    初次之外sysmon还需要处理gc、网络轮询器的逻辑

    协程切换

    go1.13版本前在如果sysmon发现需要进行调度会在函数的栈寄存器中打一个标记,这也就意味着for{}还是无法进行切换

    在此之后是通过发送、监听sigPreempt信号实现的

    相关文章

      网友评论

          本文标题:golang MPG并发模型

          本文链接:https://www.haomeiwen.com/subject/nrjszltx.html