Go routine调度详解
作者:曾纪文 发布时间:2024-05-10 10:57:57
goroutine简介
goroutine是go语言中最为NB的设计,也是其魅力所在,goroutine的本质是协程,是实现并行计算的核心。goroutine使用方式非常的简单,只需使用go关键字即可启动一个协程,并且它是处于异步方式运行,你不需要等它运行完成以后在执行以后的代码。
go func()//通过go关键字启动一个协程来运行函数
go routine的调度原理和操作系统的线层调度是比较相似的。这里我们将介绍go routine的相关知识。
goroutine(有人也称之为协程)本质上go的用户级线程的实现,这种用户级线程是运行在内核级线程之上。当我们在go程序中创建goroutine的时候,我们的这些routine将会被分配到不同的内核级线程中运行。一个内核级线程可能会负责多个routine的运行。而保证这些routine在内内核级线程安全、公平、高效运行的工作,就由调度器来实现。
goroutine内部原理
概念介绍
在进行实现原理之前,了解下一些关键性术语的概念。
并发
一个cpu上能同时执行多项任务,在很短时间内,cpu来回切换任务执行(在某段很短时间内执行程序a,然后又迅速得切换到程序b去执行),有时间上的重叠(宏观上是同时的,微观仍是顺序执行),这样看起来多个任务像是同时执行,这就是并发。
并行
当系统有多个CPU时,每个CPU同一时刻都运行任务,互不抢占自己所在的CPU资源,同时进行,称为并行。
进程
cpu在切换程序的时候,如果不保存上一个程序的状态(也就是我们常说的context--上下文),直接切换下一个程序,就会丢失上一个程序的一系列状态,于是引入了进程这个概念,用以划分好程序运行时所需要的资源。因此进程就是一个程序运行时候的所需要的基本资源单位(也可以说是程序运行的一个实体)。
线程
cpu切换多个进程的时候,会花费不少的时间,因为切换进程需要切换到内核态,而每次调度需要内核态都需要读取用户态的数据,进程一旦多起来,cpu调度会消耗一大堆资源,因此引入了线程的概念,线程本身几乎不占有资源,他们共享进程里的资源,内核调度起来不会那么像进程切换那么耗费资源。
协程
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作执行者则是用户自身程序,goroutine也是协程。
Go调度的组成
Go的调度主要有四个结构组成,分别是:
G:goroutine的核心结构,包括routine的栈、程序计数器pc、以及一些状态信息等;
M:内核级线程。goroutine在M上运行。M中信息包括:正在运行的goroutine、等待运行的routine列表等。当然也包括操作系统线程相关信息,这些此处不讨论。
P:processor,处理器,只要用于执行goroutine,维护了一个goroutine列表。其实P是可以从属于M的。当P从属于(分配给)M的时候,表示P中的某个goroutine得以运行。当P不从属于M的时候,表示P中的所有goroutine都需要等待被安排到内核级线程运行。
Sched:调度器,存储、维护M,以及一个全局的goroutine等待队列,以及其他状态信息。
Go程序的启动过程
初始化Sched:一个存储P的列表pidle。P的数量可以通过GOMAXPROCS设置;
创建第一个goroutine。这个goroutine会创建一个M,这个内核级线程(sysmon)的工作是对goroutine进行监控。之后,这个goroutine开始我们在main函数里面的代码,此时,该goroutine就是我们说的主routine。
创建goroutine:
goroutine创建时指定了代码段
然后,goroutine被加入到P中去等待运行。
这个新建的goroutine的信息包含:栈地址、程序计数器
创建内核级线程M
内核级线程由go的运行时根据实际情况创建,我们无法再go中创建内核级线程。那什么时候回创建内核级线程呢?当前程序等待运行的goroutine数量达到一定数量及存在空闲(为被分配给M)的P的时候,Go运行时就会创建一些M,然后将空闲的P分配给新建的内核级线程M,接着才是获取、运行goroutine。创建M的接口函数如下:
// 创建M的接口函数
void newm(void (*fn)(void), P *p)
// 分配P给M
if(m != &runtime·m0) {Â
acquirep(m->nextp);
m->nextp = nil;
}
// 获取goroutine并开始运行
schedule();
M的运行
static void schedule(void)
{
G *gp;
gp = runqget(m->p);
if(gp == nil)
gp = findrunnable();
// 如果P的类别不止一个goroutine,且调度器中有空闲的的P,就唤醒其他内核级线程M
if (m->p->runqhead != m->p->runqtail &&
runtime·atomicload(&runtime·sched.nmspinning) == 0 &&
runtime·atomicload(&runtime·sched.npidle) > 0) // TODO: fast atomic
wakep();
// 执行goroutine
execute(gp);
}
runqget: 从P中获取goroutine即gp。gp可能为nil(如M刚创建时P为空;或者P的goroutine已经运行完了)。
findrunnable:寻找空闲的goroutine(从全局的goroutine等待队列获取goroutine;如果所有goroutine都已经被分配了,那么从其他M的P的goroutine的goroutine列表获取一些)。如果获取到goroutine,就将他放入P中,并执行它;否则没能获取到任何的goroutine,该内核级线程进行系统调用sleep了。
wakep:当当前内核级线程M的P中不止一个goroutine且调度器中有空闲的的P,就唤醒其他内核级线程M。(为了找些空闲的M帮自己分担)。
Routine状态迁移
前面说的是G,M是怎样创建的以及什么时候创建、运行。那么goroutine在M是是怎样进行调度的呢?这个才是goroutine的调度核心问题,即上面代码中的schedule。在说调度之前,我们必须知道goroutine的状态有什么,以及各个状态之间的关系。
Gidle:创建中的goroutine,实际上这个状态没有什么用;
Grunnable:新创建完成的goroutine在完成了资源的分配及初始化后,会进入这个状态。这个新创建的goroutine会被分配到创建它的M的P中;
Grunning:当Grunnable中的goroutine等到了空闲的cpu或者到了自己的时间片的时候,就会进入Grunning状态。这个装下的goroutine可以被前文提到的findrunnable函数获取;
Gwaiting:当正在运行的goroutine进行一些阻塞调用的时候,就会从Grunning状态进入Gwaiting状态。常见的调用有:写入一个满的channel、读取空的channel、IO操作、定时器Ticker等。当阻塞调用完成后,goroutine的状态就会从Gwaiting转变为Grunnable;
Gsyscall:当正在运行的goroutine进行系统调用的时候,其状态就会转变为Gsyscall。当系统调用完成后goroutine的状态就会变为Grunnable。(前文提到的sysmon进程会监控所有的P,如果发现有的P的系统调用是阻塞式的或者执行的时间过长,就会将P从原来的M分离出来,并新建一个M,将P分配给这个新建的M)。
来源:https://segmentfault.com/a/1190000017783713
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 双向数据绑定指的是当对象的属性发生变化时能够同时改变对应的UI,反之亦然。换句话说,如果我们有一个user对象,这个对象有一个name属性,
- Asterisk 是一个开放源代码的软件VoIP PBX系统,我们用Asterisk 搭建企业内部电话系统。Asterisk AMI的Ast
- 创建之前项目之前 记得改一下 maven 提高下载Pom速度 记得 setting 中
- 本文实例讲述了CentOS7环境下源码安装MySQL5.7的方法。分享给大家供大家参考,具体如下:安装依赖包yum -y install a
- 首先是准备工作Python 2.7.11:下载pythonPycharm:下载Pycharm其中python2和python3目前同步发行,
- # 查看下centos7.6上的python版本[root@registry ~]# cat /etc/redhat-releaseLinu
- 学习编写简练、优化的CSS需要大量的实践和一种不自觉的强迫性清洁的渴望。然而让你的CSS保持整洁并不仅仅是你对清洁的疯狂的心理需求,尤其对于
- 不过不得不说,datetime模块也有一些限制。 例如,当我们处理时区时,通常会显得短缺。有时,我们不得不引入一些第三方库作为补充。 此外,
- 在US BlackHat 2018大会上,安全人员证明,攻击者不仅可以利用PHAR包发动RCE攻击,而且,通过调整其二进制内容,他们还可以将
- 1. 安装完整的vim# apt-get install vim-gnome2. 安装ctags,ctags用于支持taglist,必需!#
- 1. 引言本文重点介绍Python中的三个特殊函数Map,Filter和Reduce,以及如何使用它们进行代码编程。在开始介绍之前,我们先来
- 通过指定pandas.DataFrame和pandas.Series的index(下标),可以选择和获取行/列或元素的值。根据[]中指定的值
- 一、绘制折线图使用plot()绘制折线图常用的参数:x:表示x轴的数据y:表示y轴的数据fmt:表示快速设置条样式的格式字符串。label:
- 如果按本文操作遇到一些问题报错,如C:\Users\milyyy\AppData\Roaming\npm-cache\_logs\2018-
- 本文实例讲述了python创建关联数组(字典)的方法。分享给大家供大家参考。具体分析如下:关联数组在python中叫字典,非常有用,下面是定
- python应该是近几年比较火的语言之一,很多人刚学python不知道该如何学习,尤其是没有编程基础想要从事程序员工作的小白,想必应该都会有
- 笔者今天就谈谈自己对这两种操作模式的理解,并且给出一些可行的建议,跟大家一起来提高Oracle数据库的安全性。 一、非归档模式的利与弊。 非
- Access爱好者以会VBa为荣。我觉得这不是好现象。vba只是vb的子集,有着很多限制,比如不支持继承,不支持指针,不支持子界类型等。使用
- 简介testing是 Go 语言标准库自带的测试库。在 Go 语言中编写测试很简单,只需要遵循 Go 测试的几个约定,与编写正常的 Go 代
- 那么Python如何快速上手?找来了一篇广受好评的新语言学习方法介绍,供大家参考。听说,你决定要为你的 “技能树” 再添加一门特定的编程语言