今天我想讲的就是,全靠浪的语言Golang(够浪) 调度器的实现,那么为什么Golang要自己实现那,OS内核不是已经有一个线程调度了嘛?之所以Golang会自己去实现,我认为有两种原因,一是OS上下文切换太过耗时,二是
go-scheduler上说 golang的垃圾回收需要在内存有一致的状态。
我认为这个调度器的原理非常值得我们去研究,因为goroutine是Golang的灵魂嘛??,首先支持Golang(够浪)调度器有四个重要的结构,分别是M P G Sched
- M 代表着一个内核线程 一个M就是一个内核线程,goroutine就是跑在M之上的
- P 代表着(Processor)处理器 它的主要用途就是用来执行goroutine的,所以它也维护了一个可运行的goroutine队列,和自由的goroutine队列,里面存储了所有需要它来执行的goroutine。
- G 代表着goroutine 实际的数据结构(就是你封装的那个方法),并维护者goroutine 需要的栈、程序计数器以及它所在的M等信息。
- Seched 代表着一个调度器 它维护有存储空闲的M队列和空闲的P队列,可运行的G队列,自由的G队列以及调度器的一些状态信息等。
(PS: M P G 的结构都在 /usr/local/go/src/runtime2.go 的文件中。我的go源码放在了 /usr/local/中。
这篇文章是个基础入门,我会用一个非常简单的例子来给大家阐述调度器基本的实现原理。接下来我引用几张图片:
这张图啊!真是一语胜千言,这里我把 “土拨鼠” 代表一个G “小车”代表 P“砖块” 代表一个 G 这个特殊的“土拨鼠” 就是这个工厂的管理员,也就是Seched(调度器)每个新的goroutine都会维护这自己的栈,当程序启动的时候,调度器会创建第一个goroutine,首先,按照图片上的描述,管理员(Seched)找来土拨鼠(M) 并分配给土拨鼠(M)一个小车(P),土拨鼠开始推车小车拿走一批砖,(我们称这些砖被放在本地p可运行的G队列),这个土拨鼠就这么认真努力的开始完成自己的工作,有一天你在程序中提高一下程序的并发处理能力,创造了很多的砖(G),这时候,这么多的砖块(G)已经不够这些土拨鼠去处理了,于是调度器,就是去仓库里面找到空闲的土拨鼠,如果没有就创造一个土拨鼠,当工厂管理员发现,有个土拨鼠处理的很慢,这个时候就会让他去处理,但需要让出小车,调度器会找到一个新的土拨鼠去处理这个推车上的G,当土拨鼠处理完这些G的时候就需要调度器可运行的G队列总去找,当土拨鼠实在找不到goroutine就去别人的小车中枪走一半,如果多次都抢不到,那么土拨鼠就丢弃小车回到仓库里面睡觉(sleep)去了,如果土拨鼠赶上了系统调用或者网络的IO,那么土拨鼠就等待他,这时候也不能让这个土拨鼠来阻碍其他的砖(G)的运行,这时候调度器就会找来新的土拨鼠去完成剩余的工作,这时候,新的土拨鼠就会抢走车去工作 了,当这个土拨鼠的任务完成了时候发现自己的小车已经被别人抢跑了,这个土拨鼠没有什么事情,就回到仓库里面睡觉(sleep)去了。
这里我总结一下:这种调度的算法叫work stealing 这种算法适用场景是任务之间的耗时相差比较大,即有的任务很耗时,有的任务很快完成,用这种用算法很合适;如果任务的耗时很平均则不适合,因为窃取任务也是需要抢占锁的,会造成额外的消耗。
上面都使用很简单的文字来介绍,接下来我会采用Go 1.7的源码,来描述 上面内容。如果您发现有什么问题和错误,感谢您的指正。(持续更新中。。。)