内容简介:本文从宏观角度介绍了一下Go调度器的调度过程。上篇文章回顾:
本文从宏观角度介绍了一下 Go 调度器的调度过程。
上篇文章回顾: Etcd+confd通过Nginx对后端服务的注册发现
前言
随着服务器硬件迭代升级,配置也越来越高。为充分利用服务器资源,并发编程也变的越来越重要。在开始之前,需要了解一下并发(concurrency)和并行(parallesim)的区别。
并发: 逻辑上具有处理多个同时性任务的能力。
并行: 物理上同一时刻执行多个并发任务。
通常所说的并发编程,也就是说它允许多个任务同时执行,但实际上并不一定在同一时刻被执行。在单核处理器上,通过多线程共享CPU时间片串行执行(并发非并行)。而并行则依赖于多核处理器等物理资源,让多个任务可以实现并行执行(并发且并行)。
多线程或多进程是并行的基本条件,但单线程也可以用协程(coroutine)做到并发。简单将Goroutine归纳为协程并不合适,因为它运行时会创建多个线程来执行并发任务,且任务单元可被调度到其它线程执行。这更像是多线程和协程的结合体,能最大限度提升执行效率,发挥多核处理器能力。
Go编写一个并发编程程序很简单,只需要在函数之前使用一个Go关键字就可以实现并发编程。
func main() { go func(){ fmt.Println("Hello,World!") }() }
Go调度器组成
Go语言虽然使用一个 Go 关键字即可实现并发编程,但Goroutine被调度到后端之后,具体的实现比较复杂。先看看调度器有哪几部分组成。
1、G
G是 Go routine的缩写,相当于操作系统中的进程控制块,在这里就是Goroutine的控制结构,是对Goroutine的抽象。其中包括执行的函数指令及参数;G保存的任务对象;线程上下文切换,现场保护和现场恢复需要的寄存器(SP、IP)等信息。
Go不同版本Goroutine默认栈大小不同。
// Go1.11版本默认stack大小为2KB _StackMin = 2048 // 创建一个g对象,然后放到g队列 // 等待被执行 func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) { _g_ := getg() _g_.m.locks++ siz := narg siz = (siz + 7) &^ 7 _p_ := _g_.m.p.ptr() newg := gfget(_p_) if newg == nil { // 初始化g stack大小 newg = malg(_StackMin) casgstatus(newg, _Gidle, _Gdead) allgadd(newg) } // 以下省略}
2、M
M是一个线程或称为Machine,所有M是有线程栈的。如果不对该线程栈提供内存的话,系统会给该线程栈提供内存(不同操作系统提供的线程栈大小不同)。当指定了线程栈,则M.stack→G.stack,M的PC寄存器指向G提供的函数,然后去执行。
type m struct { /* 1. 所有调用栈的Goroutine,这是一个比较特殊的Goroutine。 2. 普通的Goroutine栈是在Heap分配的可增长的stack,而g0的stack是M对应的线程栈。 3. 所有调度相关代码,会先切换到该Goroutine的栈再执行。 */ g0 *g curg *g // M当前绑定的结构体G // SP、PC寄存器用于现场保护和现场恢复 vdsoSP uintptr vdsoPC uintptr // 省略…}
3、P
P(Processor)是一个抽象的概念,并不是真正的物理CPU。所以当P有任务时需要创建或者唤醒一个系统线程来执行它队列里的任务。所以P/M需要进行绑定,构成一个执行单元。
P决定了同时可以并发任务的数量,可通过GOMAXPROCS限制同时执行用户级任务的操作系统线程。可以通过runtime.GOMAXPROCS进行指定。在Go1.5之后GOMAXPROCS被默认设置可用的核数,而之前则默认为1。
// 自定义设置GOMAXPROCS数量 func GOMAXPROCS(n int) int { /* 1. GOMAXPROCS设置可执行的CPU的最大数量,同时返回之前的设置。 2. 如果n < 1,则不更改当前的值。 */ ret := int(gomaxprocs) stopTheWorld("GOMAXPROCS") // startTheWorld启动时,使用newprocs。 newprocs = int32(n) startTheWorld() return ret }
// 默认P被绑定到所有CPU核上 // P == cpu.cores func getproccount() int32 { const maxCPUs = 64 * 1024 var buf [maxCPUs / 8]byte // 获取CPU Core r := sched_getaffinity(0, unsafe.Sizeof(buf), &buf[0]) n := int32(0) for _, v := range buf[:r] { for v != 0 { n += int32(v & 1) v >>= 1 } } if n == 0 { n = 1 } return n } // 一个进程默认被绑定在所有CPU核上,返回所有CPU core。 // 获取进程的CPU亲和性掩码系统调用 // rax 204 ; 系统调用码 // system_call sys_sched_getaffinity; 系统调用名称 // rid pid ; 进程号 // rsi unsigned int len // rdx unsigned long *user_mask_ptr sys_linux_amd64.s: TEXT runtime·sched_getaffinity(SB),NOSPLIT,$0 MOVQ pid+0(FP), DI MOVQ len+8(FP), SI MOVQ buf+16(FP), DX MOVL $SYS_sched_getaffinity, AX SYSCALL MOVL AX, ret+24(FP) RET
Go调度器调度过程
首先创建一个G对象,G对象保存到P本地队列或者是全局队列。P此时去唤醒一个M。P继续执行它的执行序。M寻找是否有空闲的P,如果有则将该G对象移动到它本身。接下来M执行一个调度循环(调用G对象->执行->清理线程→继续找新的Goroutine执行)。
M执行过程中,随时会发生上下文切换。当发生上线文切换时,需要对执行现场进行保护,以便下次被调度执行时进行现场恢复。Go调度器M的栈保存在G对象上,只需要将M所需要的寄存器(SP、PC等)保存到G对象上就可以实现现场保护。当这些寄存器数据被保护起来,就随时可以做上下文切换了,在中断之前把现场保存起来。如果此时G任务还没有执行完,M可以将任务重新丢到P的任务队列,等待下一次被调度执行。当再次被调度执行时,M通过访问G的vdsoSP、vdsoPC寄存器进行现场恢复(从上次中断位置继续执行)。
1、P 队列 通过上图可以发现,P有两种队列:本地队列和全局队列。
-
本地队列: 当前P的队列,本地队列是Lock-Free,没有数据竞争问题,无需加锁处理,可以提升处理速度。
-
全局队列: 全局队列为了保证多个P之间任务的平衡。所有M共享P全局队列,为保证数据竞争问题,需要加锁处理。相比本地队列处理速度要低于全局队列。
2、 上线文切换
简单理解为当时的环境即可,环境可以包括当时程序状态以及变量状态。例如线程切换的时候在内核会发生上下文切换,这里的上下文就包括了当时寄存器的值,把寄存器的值保存起来,等下次该线程又得到cpu时间的时候再恢复寄存器的值,这样线程才能正确运行。
对于代码中某个值说,上下文是指这个值所在的局部(全局)作用域对象。相对于进程而言,上下文就是进程执行时的环境,具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存(堆栈)信息等。
3、线程清理 Goroutine被调度执行必须保证P/M进行绑定,所以线程清理只需要将P释放就可以实现线程的清理。什么时候P会释放,保证其它G可以被执行。P被释放主要有两种情况。
-
主动释放: 最典型的例子是,当执行G任务时有系统调用,当发生系统调用时M会处于Block状态。调度器会设置一个超时时间,当超时时会将P释放。
-
被动释放: 如果发生系统调用,有一个专门监控程序,进行扫描当前处于阻塞的P/M组合。当超过系统程序设置的超时时间,会自动将P资源抢走。去执行队列的其它G任务。
总结
本文从宏观角度介绍了一下Go调度器的调度过程。Go调度器也是Go语言最精华的部分,希望对大家有所帮助。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Java多线程并发:进程调度算法
- GO 中的调度:第三部分 - 并发
- Golang 并发问题(五)goroutine 的调度及抢占
- Goroutine 并发调度模型深入之实现一个协程池
- Golang实现简单爬虫框架(4)——队列实现并发任务调度
- Golang随谈——浅瞰底层:Go的并发调度模型
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
WebWork in Action
Jason Carreira、Patrick Lightbody / Manning / 01 September, 2005 / $44.95
WebWork helps developers build well-designed applications quickly by creating re-usable, modular, web-based applications. "WebWork in Action" is the first book to focus entirely on WebWork. Like a tru......一起来看看 《WebWork in Action》 这本书的介绍吧!