Skip to content

Commit

Permalink
Merge pull request #22 from liangzuobin/master
Browse files Browse the repository at this point in the history
5.2
  • Loading branch information
tiancaiamao committed Mar 1, 2017
2 parents cf7f7ae + 4e58843 commit f295cbd
Showing 1 changed file with 12 additions and 7 deletions.
19 changes: 12 additions & 7 deletions zh/05.2.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# 5.2 goroutine的生老病死

本小节将通过goroutine的创建,消亡,阻塞和恢复等过程,来观察Go语言的调度策略,这里就称之为生老病死吧。整个的Go语言的调度系统是比较复杂的,为了避免结构体M和结构体P引入的其它干扰,这里主要将注意力集中到结构体G中,以goroutine为主线。
本小节将通过goroutine的创建,消亡,阻塞和恢复等过程,来观察Go语言的调度策略,这里就称之为生老病死吧。整个Go语言的调度系统是比较复杂的,为了避免结构体M和结构体P引入的其它干扰,这里主要将注意力集中到结构体G中,以goroutine为主线。

## goroutine的创建
前面讲函数调用协议时说过go关键字最终被弄成了runtime.newproc。这就是一个goroutine的出生,所有的新的goroutine都是通过这个函数创建的
前面讲函数调用协议时说过go关键字最终被弄成了runtime.newproc。这就是一个goroutine的出生,所有新的goroutine都是通过这个函数创建的

runtime.newproc(size, f, args)功能就是创建一个新的g,这个函数不能用分段栈,因为它假设参数的放置顺序是紧接着函数f的(见前面函数调用协议一章,有关go关键字调用时的内存布局)。分段栈会破坏这个布局,所以在代码中加入了标记#pragma textflag 7表示不使用分段栈。它会调用函数newproc1,在newproc1中可以使用分段栈。真正的工作是调用newproc1完成的。newproc1的进行下面这些动作
runtime.newproc(size, f, args)功能就是创建一个新的g,这个函数不能用分段栈,因为它假设参数的放置顺序是紧接着函数f的(见前面函数调用协议一章,有关go关键字调用时的内存布局)。分段栈会破坏这个布局,所以在代码中加入了标记#pragma textflag 7表示不使用分段栈。它会调用函数newproc1,在newproc1中可以使用分段栈。真正的工作是调用newproc1完成的。newproc1进行下面这些动作

首先,它会检查当前结构体M中的P中,是否有可用的结构体G。如果有,则直接从中取一个,否则,需要分配一个新的结构体G。如果分配了新的G,需要将它挂到runtime的相关队列中。

获取了结构体G之后,将调用参数保存到g的栈,将sp,pc等上下文环境保存在g的sched域,这样整个goroutine就准备好了,整个状态一个goroutine运行时被中断时一样,只要等到分配CPU,它就可以继续运行。
获取了结构体G之后,将调用参数保存到g的栈,将sp,pc等上下文环境保存在g的sched域,这样整个goroutine就准备好了,整个状态和一个运行中的goroutine被中断时一样,只要等分配到CPU,它就可以继续运行。

newg->sched.sp = (uintptr)sp;
newg->sched.pc = (byte*)runtime·goexit;
Expand Down Expand Up @@ -41,17 +41,17 @@ runtime.newm功能跟newproc相似,前者分配一个goroutine,而后者分配

既然线程是以runtime.mstart为入口的,那么接下来看mstart函数。

mstart是runtime.newosproc新建的系统线程的入口地址,新线程执行时会从这里开始运行。新线程的执行和goroutine的执行是两个概念,由于有m这一层对机器的抽象,是m在执行g而不是线程在执行g。所以线程的入口是mstart,g的执行要到schedule才算入口。函数mstart的最后调用了schedule
mstart是runtime.newosproc新建的系统线程的入口地址,新线程执行时会从这里开始运行。新线程的执行和goroutine的执行是两个概念,由于有m这一层对机器的抽象,是m在执行g而不是线程在执行g。所以线程的入口是mstart,g的执行要到schedule才算入口。函数mstart最后调用了schedule

终于到了schedule了!

如果从mstart进入到schedule的,那么schedule中逻辑非常简单,大概就这几步:
如果是从mstart进入到schedule的,那么schedule中逻辑非常简单,大概就这几步:

找到一个等待运行的g
如果g是锁定到某个M的,则让那个M运行
否则,调用execute函数让g在当前的M中运行

execute会恢复newproc1中设置的上下文,这样就跳转到新的goroutine去执行了。从newproc一直出生一直到运行的过程分析,到此结束!
execute会恢复newproc1中设置的上下文,这样就跳转到新的goroutine去执行了。从newproc出生一直到运行的过程分析,到此结束!

虽然按这样a调用b,b调用c,c调用d,d调用e的方式去分析源代码谁看都会晕掉,但还是要重复一遍这里的读代码过程,希望感兴趣的读者可以拿着注释过的源码按顺序走一遍:

Expand Down Expand Up @@ -117,3 +117,8 @@ goroutine的消亡比较简单,注意在函数newproc1,设置了fnstart为go
goroutine的状态变迁图:

![](images/5.2.goroutine_state.jpg?raw=true)

## links
* [目录](<preface.md>)
* 上一节: [调度器相关数据结构](<05.1.md>)
* 下一节: [设计与演化](<05.3.md>)

0 comments on commit f295cbd

Please sign in to comment.