关键路径寻找 c语言,goroutine调度源码阅读笔记

以下为本人阅读goroutine调度源码随手记的笔记,现在还是一个个知识点的形式,暂时还没整理,先发到这里,一点点更新:

1). runq [256]guintptr P 的runable队列最大只能保存256个G

2). 全局的可执行队列由schedt持有,runq gQueue

3). goroutine 状态从_Grunning转为_Grunnable后,解除g与m的关联,将g放入全局的任务队列,然后再找到一个新的_Grunable状态的G来执行

1 func goschedImpl(gp *g) {2 status :=readgstatus(gp)3 if status&^_Gscan !=_Grunning {4 dumpgstatus(gp)5 throw("bad g status")6 }7 casgstatus(gp, _Grunning, _Grunnable)8 dropg()9 lock(&sched.lock)10 globrunqput(gp)11 unlock(&sched.lock)12

13 schedule()14 }

4). 寻找下一个g来执行的函数 findrunnable 在proc.go:2178

大致分为三大步骤,一是尝试从本地队列获取一个,二是尝试从全局队列获取n个,三是尝试窃取。这里要说一下的是:从本地队列获取时和从全局队列获取时关于并发访问的控制是不一样的,从本地队列获取的时候,都是通过原子操作来和自旋锁来实现的,而从全局队列获取时,则是通过获取sched.lock锁控制的,这是一个全局的排他锁。

首先尝试从本地队列获取(本地队列先看runnext是否不为0,如果是,先返回runnext指向的g,否则从队首弹一个g出来),如果本地队列没有则从全局队列获取,如果全局队列也没有,就尝试从其他P的队列中窃取,但是窃取之前会先做如下几个判断:

1. sched.npidle == gomaxprocs – 1 也就是看其他的P是不是都空闲着,如果是的话,就没必要去窃取了。

2. 2*sched.nmspinning >= gomaxprocs – npidle 当前处于spinning状态的M的两倍 大于等于非空闲的P的数量. 翻译过来就是在正在窃取G的M的数量应该不超过非空闲状态的P的一半

3. 是否准备执行GC,如果是,就先不窃取了

5).从全局队列取一部分g到本地队列的操作。

源码位置: proc.go:4665 globrunqget (go1.12)

关于取多少个的问题。假设全局队列一共有t个G,一共有n个P,则,取的数量不超过t / n,并且不超过本队队列总长度的一半,目前1.14版本中,本地队列总长度为256,也就是每次从全局队列取的G的数量不超过128个.

6).关于选取哪个P进行窃取以及窃取多少个的问题:

源码位置:proc.go:2238 findrunnable(go1.12)

1. 选取窃取的P

首先一共进行4轮尝试,每轮都会遍历所有的P。

另外这里的遍历P的过程值得说一下,并不是从下标0位置一个个往后遍历的,而且跳跃式的遍历,而且跳跃的步长是随机出来的。关于步长的选取,算法是这样的:

首先定义了一个randomOrder结构如下:

type randomOrder struct {

count uint32 // P的数量

coprimes []uint32 // 小于等于count,且与count互质的数(从小到大排列)

}

首先会生成一个与P的数量互质的数的列表,然后随机出一个数i,遍历P列表的步长= i % len(coprimes)。开始遍历的位置=i % nprocs (nproc即P的数量,源码中是用i对count取模,而count的值是通过randomOrder.reset操作把nprocs赋值给count的).

有意思的是,当第一轮遍历完了之后,发现一个g都没拿到,这个时候就会把stealRunNextG的值置为true,这个值表示是否从别的P的runnext窃取,第一轮窃取的时候这个值都为false。(也就是饥不择食了)

7). 窃取数量

源码位置: runqgrab proc.go:4858

假设现在P1从P2窃取一部分G。关于窃取多少个G是怎么定的呢?有以下几点原则,第一,假设P2的可执行队列中G的数量为x个,窃取个数n = x – x / 2;第二不得超过P1的可执行队列(环形buffer)容量的一半,当前最新版本(go1.14)中P的可执行队列最大容量为256,因此,窃取G的个数不超过128个。

8). 最终没有找到可执行的G之后发生了什么

1. 断开m和p之间的联系

2. 把P放进sched.pidle(空闲P的链表)

3. 把M的spinning(是否正在获取可用G)状态改为false

4. 再次确认一遍所有的P的runq都是空的

5. 尝试轮训阻塞事件列表,看看是否有一些G已经转变为runable状态.此处获取到的gList将尝试空闲的P列表中取一个P出来,然后把gList放进P的runq。而如果没有空闲的P,那么将把gList放进全局的runq。

6. 若步骤5仍然没能获取到可执行的G,或者步骤5获取到了G,但是没有空闲的P,那么将这部分G放到全局队列后,M将停止运行,将当前M加入到空闲M列表,然后开始睡眠,直到下一次被唤醒。

9).全局队列的来源有以下几种:

1. 轮询等待队列,解除等待之后将放入全局队列

2. 在M上正常执行的g,被切换之后被放入全局队列

3. 插入新的goroutine时,如果本地队列已满,则会将本地队列的一半取出来,再加上新加入的g,一起放入全局队列,放入全局队列前,会将取出来的这个列表随机打乱顺序再放入proc.go:4803(go1.12)

10).一个go程序在64位机上,最小的栈空间大小是2048个字节,最大的栈空间大小是1G,在32位机上是250M,不过这里要注意,这里的1GB并不等于1024M,而是10的9次方字节,proc.go中有这样一句话:Using decimal instead of binary GB and MB because they look nicer in the stack overflow failure message.因为看起来更好看。

11).go关键字新建一个协程时,传的参数大小是有限制的,最大是2048个字节,实际上比这个还要小一点(经测试,参数最大为2000是没问题的,2001个字节的时候,会抛出这个错误:newproc: function arguments too large for new goroutine.

12).新建G时,不会总是重新分配,而是先尝试从本地空闲的G 列表里面取一个,如果本地空闲G列表没有,则从全局空闲G列表中取,如果都没取到,就重新分配一个。

13).关于新创建的G会放到哪个P的队列的问题

以下是runqput函数的注释,在创建新的G之后,传入的next始终是true的,所以,新创建的G会放到runnext,因此,新创建的G会在当前的G被换出后,立即执行,不用排队。

// runqput tries to put g on the local runnable queue.

// If next is false, runqput adds g to the tail of the runnable queue.

// If next is true, runqput puts g in the _p_.runnext slot.

// If the run queue is full, runnext puts g on the global queue.

14). sudog用来保存g的等待队列,例如channel的发送或接收等待队列

type sudog struct {

g *g

isSelect bool

next *sudog

prev *sudog

elem unsafe.Pointer // data element (may point to stack)

acquiretime int64

releasetime int64

ticket uint32

parent *sudog // semaRoot binary tree

waitlink *sudog // g.waiting list or semaRoot

waittail *sudog // semaRoot

c *hchan // channel

}

程序员灯塔

转载请注明原文链接:goroutine调度源码阅读笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#include #include #define N1 9 #define N2 8 #define T N1*N2 #define M 4 char B[N1+1][N2+1]; int count=0; //记录路径条数 typedef struct node1 { int a1; int a2; }find,direct[M+1]; typedef struct { int b1; int b2; int id; }site; typedef struct //顺序栈 { site ht[T]; int top; }Stack; void Push(Stack *s,int a,int b) { s->top++; s->ht[s->top].b1=a; s->ht[s->top].b2=b; } void Gettop(Stack * s,int *a,int *b) { *a=s->ht[s->top].b1; *b=s->ht[s->top].b2; } void create(char *a) //从文件读出迷宫(正确) { int i=0,j=0,p=1; char x; FILE *fp; fp=fopen("in.txt","r"); if(fp==NULL) { printf("文件不能打开!\n"); exit(0); } x=fgetc(fp); while(x!=EOF) { if(x=='0') { i++; a[i]=x; } if(x=='1') { i++; a[i]=x; } x=fgetc(fp); } printf(" ~~~~~~~生成迷宫~~~~~~~\n"); x=fgetc(fp); while(p<=T) //用二维数组b记录迷宫每个位置是否可行 { for(i=1;i<=N1;i++) for(j=1;j<=N2;j++) { B[i][j]=a[p]; p++; } } printf(" "); printf("■■■■■■■■■■■■\n"); printf(" ■"); printf(" ■\n"); for(i=1;i<=N1;i++) { printf(" "); printf("■ "); for(j=1;jht[s1->top].id=id; B[x][y]='*'; while(s1->top>0) { Gettop(s1,&x,&y); id=s1->ht[s1->top].id; if(x==B1&&y==B2) { count++; fprintf(fp,"%d%c%c",count,':',' '); printf("第 %d 条路径(长度为%d):\n",count,s1->top); s1->ht[s1->top].id=0; for(i=1;itop;i++) { printf("(%d,%d,%d)->",s1->ht[i].b1,s1->ht[i].b2,s1->ht[i].id); fprintf(fp,"%c%d%c%d%c%d%c%c",'(',s1->ht[i].b1,',',s1->ht[i].b2,',',s1->ht[i].id,')',' '); if(i==0) fprintf(fp,"%c%c%c%c",'\n',' ',' ',' '); if(i%8==0) printf("\n"); } fprintf(fp,"%c",'\n'); printf("结束!\n\n"); if(s1->toptop=s1->top; min=s1->top; for(i=1;itop;i++) s2->ht[i]=s1->ht[i]; } B[x][y]='0'; s1->top--; //退栈(s1->top--) Gettop(s1,&x,&y); id=s1->ht[s1->top].id; } fun=0; while(idht[s1->top].b1; y=s1->ht[s1->top].b2; x=x+p[id].a1; y=y+p[id].a2; if(x==0||y==0||x>N1||y>N2) continue; if(B[x][y]=='0') { fun=1; break; } } if(fun==1) //找到通路 { s1->ht[s1->top].id=id; Push(s1,x,y); B[x][y]='*'; s1->ht[s1->top].id=0; } else { x=s1->ht[s1->top].b1; y=s1->ht[s1->top].b2; B[x][y]='0'; s1->top--; } } if(count==0) printf(" 无路径!\n"); else { printf("\n\n\n "); printf("所有路径已存储在文件%s 中,请去查找!\n\n",filename); } return 1; } void Print(Stack *s2,char filename[]) { int i; FILE *fp; fp=fopen(filename,"a+"); if(fp==NULL) { printf("文件不能打开!\n"); exit(0); } if(count!=0) { fprintf(fp,"%s","最短路径为:"); fprintf(fp,"%c",'\n'); printf(" "); printf("%s\n","**********最短路径**********\n"); for(i=1;itop;i++) { printf("(%d,%d,%d) ->",s2->ht[i].b1,s2->ht[i].b2,s2->ht[i].id); fprintf(fp,"%c%d%c%d%c%d%c%c",'(',s2->ht[i].b1,',',s2->ht[i].b2,',',s2->ht[i].id,')',' '); if(i==0) fprintf(fp,"%c",'\n'); if(i%7==0) printf("\n"); } fprintf(fp,"%c",'\n'); printf("结束!\n"); printf("(最短路径长度: %d)\n",s2->top); } } void main() //主函数 { char a[T+1]; //二维数组b记录迷宫的每个位置 char filename1[20],filename2[20]; int x1,x2,y1,y2,k; Stack *s1,*s2; direct f1; f1[1].a1=0; f1[1].a2=1; //判断方向(右) f1[2].a1=1; f1[2].a2=0; //(下) f1[3].a1=0; f1[3].a2=-1; //(左) f1[4].a1=-1; f1[4].a2=0; //(上) s1=(Stack *)malloc(sizeof(Stack)); s2=(Stack *)malloc(sizeof(Stack)); s1->top=0; //指向栈顶(初始化栈) s2->top=0; create(a); printf("\n\n "); printf("请输入入口坐标: "); scanf("%d%d",&x1,&x2); printf(" "); printf("请输入出口坐标: "); scanf("%d%d",&y1,&y2); printf(" "); printf("请输入存储所有路径的文件名:"); scanf("%s",filename1); printf(" "); printf("请输入存储最短路径的文件名:"); scanf("%s",filename2); system("cls"); k=search(x1,x2,y1,y2,s1,s2,f1,filename1); if(k==1) Print(s2,filename2); printf("\n"); }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值