2021-03-31 go语言的goroutine调度

  1. go语言的调度设计到几个重要是数据结构,结构体g,结构体m,结构体p以及sched结构体,

结构体G

     

struct G
{
    uintptr    stackguard;    // 分段栈的可用空间下界
    uintptr    stackbase;    // 分段栈的栈基址
    Gobuf    sched;        //进程切换时,利用sched域来保存上下文
    uintptr    stack0;
    FuncVal*    fnstart;        // goroutine运行的函数
    void*    param;        // 用于传递参数,睡眠时其它goroutine设置param,唤醒时此goroutine可以获取
    int16    status;        // 状态Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
    int64    goid;        // goroutine的id号
    G*    schedlink;
    M*    m;        // for debuggers, but offset not hard-coded
    M*    lockedm;    // G被锁定只能在这个m上运行
    uintptr    gopc;    // 创建这个goroutine的go表达式的pc
    ...
};

     G是goroutine的缩写,是goroutine的控制结构,是对goroutine的抽象,其中goid是这个goroutine的id,status是这个goroutine的状态。

     上面是G结构体的部分域,可以看到一些主要信息,栈信息,这是控制goroutine需要多大的栈,利于连续栈技术的栈控件分配

      lockedm是锁定m只能在绑定的上运行,后面我们会了解到m到底是什么。

     goroutine在切换的时候,上下文保存在结构体sched域中,goroutine是轻量级的,切换时并不会陷入到操作系统内核中,所以保存过程很轻量。

struct Gobuf
{
    // The offsets of these fields are known to (hard-coded in) libmach.
    uintptr    sp;
    byte*    pc;
    G*    g;
    ...
};

看上面的gobuf结构体,只保存了当前栈指针和程序计数寄存器以及g本身。

g有一个全局的g列表

结构体M

M是machine的缩写,是对机器的抽象,一个m对应一条操作系统的物理线程。M必须关联p才能执行go代码,但是当他处理阻塞或者系统调用中时。可以不用关联p。

struct M
{
    G*    g0;        // 带有调度栈的goroutine
    G*    gsignal;    // signal-handling G 处理信号的goroutine
    void    (*mstartfn)(void);
    G*    curg;        // M中当前运行的goroutine
    P*    p;        // 关联P以执行Go代码 (如果没有执行Go代码则P为nil)
    P*    nextp;
    int32    id;
    int32    mallocing; //状态
    int32    throwing;
    int32    gcing;
    int32    locks;
    int32    helpgc;        //不为0表示此m在做帮忙gc。helpgc等于n只是一个编号
    bool    blockingsyscall;
    bool    spinning;
    Note    park;
    M*    alllink;    // 这个域用于链接allm
    M*    schedlink;
    MCache    *mcache;
    G*    lockedg;
    M*    nextwaitm;    // next M waiting for lock
    GCStats    gcstats;
    ...
};

 上面是m的部分域,m也有一个全局的m表,控制这所有的m,lockedg是某情况下,g锁定在这么m中运行不会切换到其他的m,其实就是g绑定特定的m运行。m中有一个mcache,是当前m的内存缓存。m和g一样有一个常用寄存器,代表当前的m,同时存在多个,标识同时存在多个物理线程。

结构体m可以看到有两个g,一个是curg,代表当前m绑定的结构体g,另一个g0,是待遇调度栈的goroutine,这是一个比较特殊的goroutine,普通的goroutine的栈是在堆上的可增长的栈,而g0是在m对应的线程栈上,所有的调度相关的代码会切换到该goroutine栈中再执行。

结构体p

struct P
{
    Lock;
    uint32    status;  // Pidle或Prunning等
    P*    link;
    uint32    schedtick;   // 每次调度时将它加一
    M*    m;    // 链接到它关联的M (nil if idle)
    MCache*    mcache;
    G*    runq[256];
    int32    runqhead;
    int32    runqtail;
    // Available G's (status == Gdead)
    G*    gfree;
    int32    gfreecnt;
    byte    pad[64];
};

注意,跟G不同的是,P不存在waiting状态。MCache被移到了P中,但是在结构体M中也还保留着。在P中有一个Grunnable的goroutine队列,这是一个P的局部队列。当P执行Go代码时,它会优先从自己的这个局部队列中取,这时可以不用加锁,提高了并发度。如果发现这个队列空了,则去其它P的队列中拿一半过来,这样实现工作流窃取的调度。这种情况下是需要给调用器加锁的。

Sched

struct Sched {
    Lock;
    uint64    goidgen;
    M*    midle;     // idle m's waiting for work
    int32    nmidle;     // number of idle m's waiting for work
    int32    nmidlelocked; // number of locked m's waiting for work
    int3    mcount;     // number of m's that have been created
    int32    maxmcount;    // maximum number of m's allowed (or die)
    P*    pidle;  // idle P's
    uint32    npidle;  //idle P的数量
    uint32    nmspinning;
    // Global runnable queue.
    G*    runqhead;
    G*    runqtail;
    int32    runqsize;
    // Global cache of dead G's.
    Lock    gflock;
    G*    gfree;
    int32    stopwait;
    Note    stopnote;
    uint32    sysmonwait;
    Note    sysmonnote;
    uint64    lastpoll;
    int32    profilehz;    // cpu profiling rate
}

大多数需要的信息都已放在了结构体M、G和P中,Sched结构体只是一个壳。可以看到,其中有M的idle队列,P的idle队列,以及一个全局的就绪的G队列。Sched结构体中的Lock是非常必须的,如果M或P等做一些非局部的操作,它们一般需要先锁住调度器。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值