我对于Golang中GMP模型的一点认识(三)

154 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

在本系列的前两部分中,我主要介绍了Golang中GMP模型的基本概念和一些特殊的机制,在本部分中我将从源码这个层次,来描述一下main函数运行背后的故事。

以main函数作为整个程序的入口,这应该已经是编程语言界的一个不成文规定了。网上一些技术大牛也经常是围绕main函数来展开对底层技术细节的讲解,可见掌握main函数执行的具体流程对于了解一门编程语言背后的设计哲学与理念来说至关重要。

首先会创建一个名为M0M_0的内核态线程,然后创建一个名为G0G_0的协程,接着将M0M_0G0G_0进行关联,后面将由M0M_0来负责执行初始化操作和运行第一个G(也就是G0G_0)。接下来是进行调度器的初始化,包括对M0M_0的初始化,对运行时栈的初始化,对Go中著名的GC进行的初始化,再就是对由GOMAXPROCS个P构成的P列表进行初始化。然后是真正启动M0M_0,此时M0M_0已经绑定了P,并从P的local队列中取G,此时会拿到main goroutine。G本身是有栈的,只不过栈空间比较小(2K),远小于真正的线程所拥有的栈空间大小(8M),M会根据G中提供的栈信息以及调度信息设置具体的运行环境,接着真正运行G。M运行完G后,再从P处取得可以运行的G。如此循环往复,直到main包下的main函数退出,runtime包下的main函数会执行defer与panic处理,或者是调用runtime包下的exit函数退出程序,有点类似于善后工作。

可以看到,调度永远是一套编程语言底层最重要的实现,在上述流程中,每次涉及到选择时,就会遵循上篇文章中所提及的方式进行决策,调度器的生命周期几乎和Go程序的执行全流程相伴而生。涉及到此部分源码时,可能涉及到一些CGO甚至汇编的知识,这对普通的server开发工程师可能是一种不一样的体验。