数据结构

Golang中实现协程调度算法的主要有以下三个数据结构,正是这三个结构加上一些算法构成了Golang的协程调度算法,当然,这些数据结构也是在不断进化的,保不准未来又会加入其他结构来提升调度器性能。

其中:

  • M: 代表操作系统线程,也就是我们经常理解的线程,是真正参与OS调度的单元,每个goroutine只有依附于某个M方可真正地执行;

  • G: 代表协程,也就是我们经常使用的Goroutine;

  • P: 协程调度器的基本调度单元,G的容身之所,连接G和M的桥梁。

理解调度器的关键是理解P的作用:它是实现M:N调度的关键结构。在Golang1.1版本中被引入,解决了1.0中的全局调度队列带来的可扩展性问题。

三个数据结构之间的关系如下:

1). 每个G都必须依附于某个P;
2). 每个M想运行的时候,必须先获取一个P,再执行P中的G;
3). 每个P维护可运行G队列,避免了全局G队列带来的锁竞争问题,提高了调度器的可扩展性;
4). 如果一个M在执行G的时候进入了syscall,那么该M就被占据了,与其关联的P此时就游离出来,可被同样处于idle状态的M获取到,进而P内的G得到继续执行的机会;
5). go中可通过GOMAXPROCS()来设置P的数量,一般建议设置成系统核数即可,P的数量也代表了当前活跃的M数(因进入syscall而block的M不计算在内)。

struct G {
    ......
    m *m
}

struct  M
{
    ......
    G*  curg;   // current running goroutine
    P*  p;      // attached P for executing Go code (nil if not executing Go code)
    ......
}

struct P
{
    ......
    M*  m;  // back-link to associated M (nil if idle)
    ......
    // Queue of runnable goroutines.
    uint32  runqhead;
    uint32  runqtail;
    G*  runq[256];

    // Available G's (status == Gdead)
    G*  gfree;
    ......
};

阅读上面的数据结构可发现他们三者之间的关系是这样的: g:有一个指针指向执行它的m,也即g隶属于m; m:保存了当前正在执行的g,同时还指向p,也即m隶属于p; p: 保存了当前正执行p内g的m,同时还有属于它的g的链表

results matching ""

    No results matching ""