教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Goroutine 调度模型猜想

Goroutine 调度模型猜想

发布时间:2022-03-26   编辑:jiaochengji.com
教程集为您提供Goroutine 调度模型猜想等资源,欢迎您收藏本站,我们将为您提供最新的Goroutine 调度模型猜想资源
<h2>前言</h2>

近日在学习go语言,go语言的一个重大特色就是支持协程(coroutine),即用户级线程。由运行在用户态程序实现“执行体”的调度和切换(本文将一个可并发执行的逻辑单元称为“执行体”),整个过程运行在一个或多个线程上,执行体切换过程不用“陷入”内核态,因此较为轻量。

这种方式也有一定的缺点,因为协程概念提出的很早,主流语言却没有提供支持,必有原因,这个我们以后讨论。

当然,为了更好的了解goroutine,我们就有必要谈谈goroutine调度模型的实现。不过,作者是java开发人员,对分析c及汇编源码之类的事情能力有限,虽然有很多参考资料及公开的源码,分析来分析去,我也是醉了。所以在看了一些goroutine scheduler资料后,本文从另一个角度切入,描述下一个简单的协程调度模型如何实现。最后的分析可能与goroutine实际的实现不太一致,还望大家见谅。

<h2>单线程上的协程调度模型</h2>

假设只有一个线程,该线程运行一个调度程序,调度程序从其管理的执行体集合中取出一个执行体交给线程执行。该线程执行逻辑如下:

如果考虑执行体各种非正常退出的情况:

由此我们可以确定,实现该线程逻辑需要两个抽象:执行体和调度程序。

<ul><li>执行体包含:执行体的上下文环境,执行逻辑等
</li> <li>调度程序:执行体集合(链表),执行体切换等</li> </ul>

该模型有两个问题:

<ul><li>除非执行体执行完毕、主动放弃或调用系统调用,执行体将一直“占用”线程。调度器因为没有“占用”线程,也只能干看着。这种非抢占式方式是协程调度模型的通病。
</li> <li>执行体执行过程中可能向执行体集合添加新的执行体,导致该线程负担过大。</li> </ul><h2>多个线程上协程调度模型</h2>

可以想见,如果只是运行在一个线程上,那么goroutine也太简单了,接下来谈谈运行在多个线程上的情况。

不管是运行在几个线程上,线程总得一个一个启动,下图是第一个线程的运行逻辑。当然,在第一个线程启动前,肯定要为整个调度模型做一些数据结构的初始化工作。

多个线程上调度跟单个线程调度有一个很大的不同就是:一旦执行体链表包含的执行体过多,就需要开启新的线程,转移部分执行体。反之,如果某个线程执行体链表为空,则需要从全局链表或其它线程那里“挖”执行体。

执行体本身是有状态的。

<ul><li>主动放弃执行。将执行体设置为runnable状态,然后放入到全局执行体集合。(看来执行体主动放弃执行,不仅是放弃所在线程的执行机会,还不想在本线程继续呆了)
</li> <li>channel操作和网络操作等。执行体被置为wating状态,从本线程执行体集合中剥离,直到其依赖的条件满足。
</li> <li>调用系统调用。</li> </ul>

第一个线程开始执行后,go调度模型还会创建一个sysmon线程(负责监控,不运行执行体)还有一个问题,那就是一旦执行体中调用系统调用,整个线程会被阻塞。为了不影响线程中其它执行体的执行,Go语言完全是自己封装的系统调用,所以在封装系统调用的时候,可以做不少手脚,也就是进入系统调用的时候执行entersyscall,退出后又执行exitsyscall函数。entersyscall将执行体从当前执行体链表中剥离,并将改变P的状态为syscall。Sysmon监控线程会扫描所有的P,发现一个P处于了syscall的状态,就知道这个P遇到了执行体在做系统调用,于是系统监控线程就会创建一个新的线程去把这个处于syscall的P给抢过来,开始干活,这样这个小车中的其它执行体就可以绕过之前系统调用的等待了。被抢走P的线程等系统调用返回后,发现自己的P没了,不能继续干活了,于是只能把执行系统调用的执行体放回到全局链表中,自己睡觉去了。

<h2 id="goroutine">goroutine调度模型的四个抽象</h2>

有了上述铺垫,我们再来看goroutine调度模型4个重要结构,分别是M、G、P、Sched,前三个定义在runtime.h中,Sched定义在proc.c中。

<ul><li>Sched结构就是调度器,它维护有存储M和G的队列(可能是全局的)以及调度器的一些状态信息等。
</li> <li>M代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。
</li> <li>P全称是Processor,处理器,它的主要用途就是用来执行goroutine的,所以它也维护了一个goroutine队列,里面存储了所有需要它来执行的goroutine。
</li> <li>G就是goroutine实现的核心结构了,G维护了goroutine需要的栈、程序计数器以及它所在的M等信息。</li> </ul>

这样从前到后谈下来,是不是更顺一些。

<h2>参考文献</h2>

goroutine与调度器


到此这篇关于“Goroutine 调度模型猜想”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Goroutine 调度模型猜想
简单理解 Goroutine 是如何工作的
golang int64 排序_Golang的内存模型
golang并发原理剖析
Goroutine并发调度模型深度解析&amp;手撸一个协程池
goroutine 调度器
21面试必问!Goroutine的调度原理
Golang面试精编2:并发相关
golang goroutine 通知_深入golang之---goroutine并发控制与通信
Golang并发模型

[关闭]
~ ~