在上文中介绍了golang的基本特性及数据结构,本文集中讲解golang的并发机制
golang的并发机制
golang在内核级线程基础上提供特有的两级线程模型。
logo:不要用共享内存的方式进行通信。作为替代,应该以通信作为手段来共享内存。
golang线程实现模型:
三个核心元素:M(machine,一个M代表一个内核线程称为“工作线程”),P(processor,一个P代表执行一个Go代码片段所必需的资源,“上下文环境”),G(goroutine,一个G代表一个Go代码片段,前者是对后者的一种封装)
内核M + 上下文环境P 形成G运行环境,每个P 包含一个可运行的G的队列。
约定:当前运行G的M叫做“当前M”, 与之关联的P称为“本地P”。
M:
g0: M拥有的特殊G,它管辖的内存为M的调度栈(系统栈),执行调度、垃圾回收、栈管理方面的任务。
gsignal:专用与处理信号的G,管辖信号栈。
M创建之初会加入到M列表,此时设置起始函数和预联的p,然后运行时系统为这个M专门创建一个新的内核线程并与之关联。系统通过全局M列表获取到所有M的信息,同时防止M被当作垃圾回收掉。起始函数专用于运行时系统使用这个M执行系统监控任务或者垃圾回收任务,一旦被设置就不会执行后续流程。否则就会和预联P完成关联并准备执行其他任务。
运行时系统在停止M的时候会把它放入调度器的空闲M列表。
M的自旋转状态:表示M还没有找到G来运行,退出条件:找到了可运行的G或者始终没找到G而需要停止M。一般调度器会尽量保证有一个自旋M存在,新G到来时会优先使用自旋M而不是新启动一个M或者恢复一个停止的M。
P:
Go的运行时系统会适时让P与不同的M建立或断开关联。全局P列表包含了当前运行时系统创建的所有P,当P不再与M关联时会被放入空闲P列表中(前提是它的可运行G列表为空)。
每个P都有一个可运行G队列(最大为256,队列满的时候将一半的G移动到调度器的可运行G队列中)和自由G列表(包含一些已经运行完成的G,随着G完成数目增多部分G会转移到调度器的自由G列表中)。P的自由G和调度器的自由G相当于go函数的代码段封装,仅当它们不够时才创建新的G,以最大化G的复用。
P的状态:
Pidle: 当前P未与任何M关联
Prunning: 当前P正在与某个M关联
Psyscall: 当前P中运行的G正在进行系统调用
Pgcstop:运行时系统需要停止调度
Pdead:当前P已经不会再被使用
p 的最大数目默认等于正在运行当前Go程序的逻辑cpu数目,可通过runtime.GOMAXPROCS函数改变。(两项检查:最大值不超过256、正整数并且与旧值不同)。修改时停止调度器的工作,对全局P列表中的前I(新值)个P进行检查和初始化,不足的补充,多的进行清理。
G:
一个G代表一个goroutine,go语句会变成对内部函数newproc的调用,相当于提交了一个并发任务。运行时系统有一个全局G列表,所有新建的G会第一时间加入该列表中。本地P的runnext字段保存下一个运行的G,新建一个G时,如果该字段已有一个G会被踢到该P的可运行G队列末尾,被这个G取代;如果可运行G队列已满,则这个G只能被追加到调度器的可运行G队列中。
G的状态:
Gidle:当前G刚被新分配,但未被初始化
Grunnable:当前G正在可运行队列中等待执行
Grunning:当前G正在运行
Gsyscall:当前G正在执行某个系统调用
Gwaiting:当前G正在阻塞
Gdead:当前G正在闲置
Gcopystack:当前G的栈正在被移动