Goroutine调度是一个很复杂的机制,下面尝试用简单的语言描述一下Goroutine调度机制,想要对其有更深入的了解可以去研读一下源码.
首先介绍一下GMP什么意思:
G ----------- goroutine: 即Go协程,每个go关键字都会创建一个协程.
M ---------- thread内核级线程,所有的G都要放在M上才能运行.
P ----------- processor处理器,调度G到M上,其维护了一个队列,存储了所有需要它来调度的G.
Goroutine 调度器P和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行
模型图:
避免频繁的创建、销毁线程,而是对线程的复用.
①.)work stealing机制
当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程.
如果有空闲的P,则获取一个P,继续执行G0.
如果没有空闲的P,则将G0放入全局队列,等待被其他的P调度.然后M0将进入缓存池睡眠.
如下图
GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在多个CPU上同时运行
在Go中一个goroutine最多占用CPU 10ms,防止其他goroutine被饿死.
具体可以去看另一篇文章
【Golang详解】go语言调度机制 抢占式调度
当创建一个新的G之后优先加入本地队列,如果本地队列满了,会将本地队列的G移动到全局队列里面,当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G.
协程经历过程
我们创建一个协程 go func()经历过程如下图:
说明:
G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系.M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;
一个M调度G执行的过程是一个循环机制;会一直从本地队列或全局队列中获取G
上面说到P的个数默认等于CPU核数,每个M必须持有一个P才可以执行G,一般情况下M的个数会略大于P的个数,这多出来的M将会在G产生系统调用时发挥作用.类似线程池,Go也提供一个M的池子,需要时从池子中获取,用完放回池子,不够用时就再创建一个.
work-stealing调度算法:当M执行完了当前P的本地队列队列里的所有G后,P也不会就这么在那躺尸啥都不干,它会先尝试从全局队列队列寻找G来执行,如果全局队列为空,它会随机挑选另外一个P,从它的队列里中拿走一半的G到自己的队列中执行.
如果一切正常,调度器会以上述的那种方式顺畅地运行,但这个世界没这么美好,总有意外发生,以下分析goroutine在两种例外情况下的行为.
Go runtime会在下面的goroutine被阻塞的情况下运行另外一个goroutine:
用户态阻塞/唤醒
系统调用阻塞
当M执行某一个G时候如果发生了阻塞操作,M会阻塞,如果当前有一些G在执行,调度器会把这个线程M从P中摘除,然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P.当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列.如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中.
队列轮转
可见每个P维护着一个包含G的队列,不考虑G进入系统调用或IO操作的情况下,P周期性的将G调度到M中执行,执行一小段时间,将上下文保存下来,然后将G放到队列尾部,然后从队列中重新取出一个G进行调度.
M0
M0是启动程序后的编号为0的主线程,这个M对应的实例会在全局变量rutime.m0中,不需要在heap上分配,M0负责执行初始化操作和启动第一个G,在之后M0就和其他的M一样了
G0
G0是每次启动一个M都会第一个创建的goroutine,G0仅用于负责调度G,G0不指向任何可执行的函数,每个M都会有一个自己的G0,在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0
一个G由于调度被中断,此后如何恢复?
中断的时候将寄存器里的栈信息,保存到自己的G对象里面.当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了.
我这里只是根据自己的理解进行了简单的介绍,想要详细了解有关GMP的底层原理可以去看Go调度器 G-P-M 模型的设计者的文档或直接看源码
参考: ()
()
golang学习比较简单,不过任何一门语言都不是孤立存在的,今天这一节简要说明一下golang开发的学习路线
①golang基础,包括go语言安装,go语言语法,流程控制语句,函数,方法,面向对象概念,网络编程,并发编程等
链乔教育在线祝您学有所成.
①1 Go 安装
Go的三种安装方式
Go有多种安装方式,你可以选择自己喜欢的.这里我们介绍三种最常见的安装方式:
Go源码安装:这是一种标准的软件安装方式.对于经常使用Unix类系统的用户,尤其对于开发者来说,从源码安装可以自己定制.
第三方工具安装:目前有很多方便的第三方软件包工具,例如Ubuntu的apt-get、Mac的homebrew等.这种安装方式适合那些熟悉相应系统的用户.
最后,如果你想在同一个系统中安装多个版本的Go,你可以参考第三方工具GVM,这是目前在这方面做得最好的工具,除非你知道怎么处理.
Go源码安装
在Mac系统中,只要你安装了Xcode,就已经包含了相应的编译工具.
在类Unix系统中,需要安装gcc等工具.例如Ubuntu系统可通过在终端中执行sudo apt-get install gcc
在Windows系统中,你需要安装MinGW,然后通过MinGW安装gcc,并设置相应的环境变量.
cd go/src
./all.bash
运行all.bash后出现"ALL TESTS PASSED"字样时才算安装成功.
上面是Unix风格的命令,Windows下的安装方式类似,只不过是运行all.bat,调用的编译器是MinGW的gcc.
如果是Mac或者Unix用户需要设置几个环境变量,如果想重启之后也能生效的话把下面的命令写到.bashrc或者.zshrc里面,
export GOPATH=$HOME/gopath
export PATH=$PATH:$HOME/go/bin:$GOPATH/bin
如果你是写入文件的,记得执行bash .bashrc或者bash
.zshrc使得设置立马生效.
如果是window系统,就需要设置环境变量,在path里面增加相应的go所在的目录,设置gopath变量.
当你设置完毕之后在命令行里面输入go,看到如下图片即说明你已经安装成功
图1.1 源码安装之后执行Go命令的图
如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录.
关于上面的GOPATH将在下面小节详细讲解
Go标准包安装
Go提供了每个平台打好包的一键安装,这些包默认会安装到如下目录:/usr/local/go
(Windows系统:c:\Go),当然你可以改变他们的安装位置,但是改变之后你必须在你的环境变量中设置如下信息:
export GOROOT=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
上面这些命令对于Mac和Unix用户来说最好是写入.bashrc或者.zshrc文件,对于windows用户来说当然是写入环境变量.
我们此时此刻呢的Go安装需要判断操作系统的位数,所以这小节我们先确定自己的系统类型.
Linux系统用户可通过在Terminal中执行命令arch(即uname
-m)来查看系统信息:
Mac 安装
看到类似上面源码安装成功的图片说明已经安装成功
如果出现go的Usage信息,那么说明go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了go的安装目录.
Linux 安装
假定你想要安装Go的目录为 $GO_INSTALL_DIR,后面替换为相应的目录路径.
$GO_INSTALL_DIR.
设置PATH,export PATH=$PATH:$GO_INSTALL_DIR/go/bin
然后执行go
Windows 安装
C:\Go\,若安装到其他位置会导致不能执行自己所编写的 Go 代码.安装完成后默认会在环境变量 Path 后添加 Go 安装目录下的 bin 目录
C:\Go\bin\,并添加环境变量 GOROOT,值为 Go 安装根目录 C:\Go\ .
验证是否安装成功
在运行中输入 cmd 打开命令行工具,在提示符下输入 go,检查是否能看到 Usage 信息.输入
cd %GOROOT%,看是否能进入 Go 安装目录.若都成功,说明安装成功.
不能的话请检查上述环境变量 Path 和 GOROOT 的值.若不存在请卸载后重新安装,存在请重启计算机后重试以上步骤.
第三方工具安装
GVM
gvm是第三方开发的Go多版本管理工具,类似ruby里面的rvm工具.使用起来相当的方便,安装gvm使用如下命令:
bash (curl -s -S -L )
安装完成后我们就可以安装go了:
执行完上面的命令之后GOPATH、GOROOT等环境变量会自动设置好,这样就可以直接使用了.
apt-get
Ubuntu是目前使用最多的Linux桌面系统,使用apt-get命令来管理软件包,我们可以通过下面的命令来安装Go,为了以后方便,应该把
git mercurial 也安装上:
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:gophers/go
sudo apt-get update
sudo apt-get install golang-stable git-core mercurial
homebrew
homebrew是Mac系统下面目前使用最多的管理软件的工具,目前已支持Go,可以通过命令直接安装Go,为了以后方便,应该把
brew update brew upgrade
brew install go
brew install git
brew install mercurial
选择IT培训机构的建议:
①.、培训机构的品牌实力是尤为重要的,具有品牌实力的培训机构在学员就业、师资选择上都有更多,其雄厚的资本实力、大规模的培训基地、过硬的教学水平、完善的就业流程等等,都是很多小机构所不具备的,所以说,大品牌意味着强实力.
想要了解更多有关IT培训的go语言路线图怎么看相关咨询,推荐咨询千锋教育.千锋教育成立教研学科中心,推出贴近企业需求的线下技能培训课程.采用全程面授高品质、高体验培养模式,学科大纲紧跟企业需求,拥有国内一体化教学管理及学员服务,在职业教育发展道路上不断探索前行.
学习GO并没有你想象中的那么难,更何况你还懂Java.我也是把Go当作第二语言学习的,在慕课网看那个go的学习路线,花了两个月左右,Go确实很强大也是未来的大趋势.
①.、反射可以在运行时 动态获取变量的各种信息 ,比如变量的类型、类别;
①.、不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射.
例如以下这种桥接模式:
示例第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数.
①.、reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型.
①.、reflect.Value.Kind,获取变量的 类别(Kind) ,返回的是一个 常量 .在go语言文档中:
示例如下所示:
输出如下:
Kind的范畴要比Type大.比如有Student和Consumer两个结构体,他们的 Type 分别是 Student 和 Consumer ,但是它们的 Kind 都是 struct .
如果是x是float类型的话,也是要用reflect.Value(x).Float().但是如果是struct类型的话,由于type并不确定,所以没有相应的方法,只能 断言.