大家好,我是小X,曹大最近开设了围棋课程,小X跟曹大一起围棋。本系列将讨论从课程中学到的一些有启发性的东西,拨开乌云,带你回到Go。上周开课,曹达直播第一期,干货满满,过瘾。第一节课结束后,陆续有同学加入进来。先抛出本文的结论:Go调度的本质是一个生产-消费过程。Producer-ConsumerProducer-ConsumerModel使用Go最酷的事情就是使用一个gofunc(){}()来启动一个goroutine来并发执行任务。这比使用C/C++启动一个线程并发执行任务要方便得多。这段代码其实是产生了一个goroutine,进入runnable队列,等待m找到它,让它可以运行。熟悉GMP模型的朋友都知道,goroutine最终是在m上执行的,因为操作系统是不感知goroutine的,它只能感知线程,而线程可以看作是m。因此,m获取goroutine并运行的过程是一个消费过程。生产-消费过程生产过程-三级队列产生的goroutine需要找一个地方存放,这个地方就是runnablequeue。在Go程序中,runnablequeue是有层次的,分为三级:三级runnablequeuerunnext实际上只能指向一个goroutine,所以是一个特殊的queue。您将goroutine放在哪个可运行队列中?这取决于。首先,如果runnext为空,goroutine会被成功放入runnext,然后以最高优先级运行,也就是最先被消费。如果runnext不为空,则负责先将runnext上的旧goroutine踢出,再将新goroutine放到上面。踢到哪里去了?以及得分情况。本地队列是一个大小为256的数组,实际上是作为一个头尾指针的循环数组来使用的。如果本地队列未满,则将runnext放入本地队列;否则,P的本地队列上的goroutine过多,说明P当前的任务太重,需要减少,需要辅助其他P。这样,当前P的runnext和一半的goroutine就被打包丢进了全局队列。当然,这部分课程还有非常生动的动画。这里截图给大家体验一下:Producer动画消费流程-调度循环上一篇文章也提到了调度循环是怎么回事。它实际上是一个围棋程序。启动时,会创建数量等于CPU核数的P,会创建初始的m,称为m0。这个m0会开始一个调度循环:不断的找g,执行,再找g……伪代码是这样的:随着调度循环的运行,会创建更多的m,所以执行的调度循环也会更多。那边的producer在不断的生产g,而这边m的调度循环也在不断的消耗g,整个流程开始运行。在寻找g的过程中,当然我们也是从上面的三级队列开始寻找:先看runnext,再看本地队列,再看全局队列。当然,如果找不到,就去找别的p去偷。总结今天的文章,只需要记住一点:Go调度的本质是一个生产-消费过程。这个观点很新颖。我以前没有从任何文章中看到过。这是曹达自己的感悟。读者即使从未见过类似的说法,但听到曹达一说,顿时豁然开朗。这种熟悉和惊喜的结合实际上是你成长的机会。
