Go routine调度

栏目: Go · 发布时间: 5年前

内容简介:go routine的调度原理和操作系统的线层调度是比较相似的。这里我们将介绍go routine的相关知识。goroutine(有人也称之为协程)本质上go的用户级线程的实现,这种用户级线程是运行在内核级线程之上。当我们在go程序中创建goroutine的时候,我们的这些routine将会被分配到不同的内核级线程中运行。一个内核级线程可能会负责多个routine的运行。而保证这些routine在内内核级线程安全、公平、高效运行的工作,就由调度器来实现。Go的调度主要有四个结构组成,分别是:

go routine的调度原理和操作系统的线层调度是比较相似的。这里我们将介绍go routine的相关知识。

goroutine(有人也称之为协程)本质上 go 的用户级线程的实现,这种用户级线程是运行在内核级线程之上。当我们在go程序中创建goroutine的时候,我们的这些routine将会被分配到不同的内核级线程中运行。一个内核级线程可能会负责多个routine的运行。而保证这些routine在内内核级线程安全、公平、高效运行的工作,就由调度器来实现。

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的状态有什么,以及各个状态之间的关系。

Go routine调度

  • 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)。

Ref


以上所述就是小编给大家介绍的《Go routine调度》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

引爆社群:移动互联网时代的新4C法则(第2版)

引爆社群:移动互联网时代的新4C法则(第2版)

唐兴通 / 机械工业出版社 / 69.00元

社群已经被公认为是这个时代的商业新形态,原有的商业逻辑和方法被颠覆,新的基于社群的商业体系和规则亟待构建,今天几乎所有的企业都在为此而努力,都在摸索中前行。 本书提出的“新4C法则”为社群时代的商业践行提供了一套科学的、有效的、闭环的方法论,第1版上市后获得了大量企业和读者的追捧,“新4C法则”在各行各业被大量解读和应用,积累了越来越多的成功案例,被公认为是社群时代通用的方法论。也因此,第1......一起来看看 《引爆社群:移动互联网时代的新4C法则(第2版)》 这本书的介绍吧!

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具