goroutine原理(C語言表示)

協程:

1、能夠在單一的系統線程中模擬多個任務的併發執行。
2、在一個特定的時間,只有一個任務在運行,即並非真正地並行。
3、被動的任務調度方式,即任務沒有主動搶佔時間片的說法。當一個任務正在執行時,外 部沒有辦法中止它。要進行任務切換,只能通過由該任務自身調用yield()來主動出讓 CPU使用權。
4、每個協程都有自己的堆棧和局部變量。

每個協程都包含3種運行狀態:掛起、運行和停止。停止通常表示該協程已經執行完成(包 括遇到問題後明確退出執行的情況),掛起則表示該協程尚未執行完成,但出讓了時間片,以後 有機會時會由調度器繼續執行。

示例

package main 
 
import (  
	"flag"  
	"fmt"  
	"os" 
	"strconv" 
) 
 
var goal int 
 
func primetask(c chan int) { 
 	for {         
		i := <-c 
 		fmt.Printf("%d\n",i)   
 	} 
 } 
 
func main() {
    c := make(chan int) 
    go primetask(c) 
 
	for i := 0;i < 100; i++ {         
		c <- i     
	}     
} 

對應C語言版本

#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h> 
#include <task.h> 

//goroutine函數
void primetask(void *arg) {  
	Channel *c = (Channel*)arg;  
	for(;;){   
		//從channel獲取數據
		int i = chanrecvul(c); 
		printf("%d\n",i)
	} 
} 
 
void taskmain(int argc, char **argv) {  
	int i;  
	Channel *c; 
 	//創建channel
	c = chancreate(sizeof(unsigned long), buffer);  
	//創建goroutine並調度
	taskcreate(primetask, c, 8192);  
	
	for(i=0;i < 100;++i)
		//往channel中發送數據   
		chansendul(c, i); 
} 

原理剖析:

//結構體Task表示每一個goroutine
struct Task {  
	char name[256];  //名字
	char state[256]; //狀態
	Task *next;  //上一個任務
	Task *prev;  //下一個任務
	Task *allnext;  
	Task *allprev;  
	Context context;  //上下文
	uvlong alarmtime;  
	uint id;  //id
	uchar *stk;  //棧指針
	uint stksize;  //棧大小
	int exiting;  //是否退出
	int alltaskslot;  //在任務隊列中的位置
	int system;  
	int ready;  //是否處於就緒狀態
	void (*startfn)(void*);  //回調函數
	void *startarg;  //函數參數
	void *udata; 
}; 

static int taskidgen; 
 
 //爲Task分配內存並初始化
static Task* taskalloc(void (*fn)(void*), void *arg, uint stack) {  
	Tat;  
	sigset_t zero;  
	uint x, y;  
	ulong z;
	
	/* 一起分配任務和棧需要的內存*/  
	t = malloc(sizeof *t+stack);  
	if(t == NULL){   
		fprint(2, "taskalloc malloc: %r\n");   
		abort(); 
	}  
	memset(t, 0, sizeof *t);  
	t->stk = (uchar*)(t+1);  
	t->stksize = stack;  
	t->id = ++taskidgen;  
	t->startfn = fn;  
	t->startarg = arg; 
	   
	/* 初始化 */ 
	memset(&t->context.uc, 0, sizeof t->context.uc);  
	sigemptyset(&zero);  
	sigprocmask(SIG_BLOCK, &zero, &t->context.uc.uc_sigmask); 
	  
	/* 必須使用當前的上下文初始化*/  
	if(getcontext(&t->context.uc) < 0){   
		fprint(2, "getcontext: %r\n");   
		abort();
	}  
	
	/* 調用makecontext來完成真正的工作 */ 
	/* 頭尾都留點空間。*/  
	t->context.uc.uc_stack.ss_sp = t->stk+8;  
	t->context.uc.uc_stack.ss_size = t->stksize-64;   
	print("make %p\n", t); 
	//z用於表示task的64位地址
	//y保存低32位,x保存高32位
	z = (ulong)t; 
	y = z; 
	z >>= 16;  
	x = z>>16; 
	//爲每個task的上下文綁定一個函數
	makecontext(&t->context.uc, (void(*)())taskstart, 2, y, x);   
	return t; 
} 

//每個上下文的回調函數
static void taskstart(uint y, uint x) {  
	//將x,y還原爲task的地址
	Task *t; 
	ulong z;    
	z = x<<16;  
	z <<= 16;  
	z |= y;  
	t = (Task*)z; 
	 
	//執行task的回調函數
 	//print("taskstart %p\n", t); 
 	t->startfn(t->startarg);						  
 	//print("taskexits %p\n", t); 
 	taskexit(0); 
 	//print("not reacehd\n"); 
 } 
 
 //創建一個goroutine並加入到調度隊列中
int taskcreate(void (*fn)(void*), void *arg, uint stack) {  
	int id;  
	Task *t;    
	t = taskalloc(fn, arg, stack);  
	taskcount++;  
	id = t->id;  
	//空間不足,重新分配
	if(nalltask%64 == 0){   
		alltask = realloc(alltask, (nalltask+64)*sizeof(alltask[0]));   
		if(alltask == NULL){    
			fprint(2, "out of memory\n");    
			abort();   
		}  
	}  
	//加入到調度隊列中
	t->alltaskslot = nalltask;  
	alltask[nalltask++] = t;  
	taskready(t);  
	return id; 
} 

//調度程序
static void taskscheduler(void) {  
	int i;  
	Task *t;    
	taskdebug("scheduler enter");  
	for(;;){   
		if(taskcount == 0)    
			exit(taskexitval);   
		t = taskrunqueue.head;   
		if(t == NULL){    
			fprint(2, "no runnable tasks! %d tasks stalled\n", taskcount);    
			exit(1);   
		}
		deltask(&taskrunqueue, t);   
		t->ready = 0;   
		taskrunning = t;   
		tasknswitch++;   
		taskdebug("run %d (%s)", t->id, t->name); 
		//關鍵就是這個函數,將調度器的上下文切換到goroutine的上下文,goroutine執行完後回到調度函數的上下文  
		contextswitch(&taskschedcontext, &t->context);   
		//print("back in scheduler\n");   
		taskrunning = NULL;
		if(t->exiting){    
			if(!t->system)     
				taskcount--;    
			i = t->alltaskslot;    
			alltask[i] = alltask[--nalltask];    
			alltask[i]->alltaskslot = i;    
			free(t);   
		}  
	} 
}

/*
goroutine讓出cpu的情況有3種
1.自己執行完畢
2.主動讓出(yield)
3.io阻塞
*/

//讓出函數
int taskyield(void) {  
	//主要是將自己狀態變爲ready並放回到調度隊列中,並將當前的上下文切換回調度器的上下文
	int n; 
	n = tasknswitch;  
	taskready(taskrunning);  
	taskstate("yield");  
	taskswitch();  
	return tasknswitch - n - 1; 
}

void taskswitch(void) { 
	needstack(0);  
	contextswitch(&taskrunning->context, &taskschedcontext);//切換回調度器的上下文
} 
 
void taskready(Task *t) {  
	t->ready = 1;  
	addtask(&taskrunqueue, t); 
} 


//Channel的數據結構表示
struct Alt {  
	Channel *c;  
	void *v;  
	unsigned int op;  
	Task *task;  
	Alt *xalt; 
}; 
 
struct Altarray {  
	Alt **a;  
	unsigned int n;  
	unsigned int m; 
}; 
 
struct Channel {  
	unsigned int bufsize;  
	unsigned int elemsize;  
	unsigned char *buf;  
	unsigned int nbuf;  
	unsigned int off;  
	Altarray asend;  
	Altarray arecv;  
	char *name; 
};

//Channel主要是維護了一個數據緩衝區,一個接收隊列和發送隊列

Channel* chancreate(int elemsize, int bufsize) {  
	Channel *c; 
	 
	c = malloc(sizeof *c+bufsize*elemsize);  
	if(c == NULL){   
		fprint(2, "chancreate malloc: %r");   
		exit(1);  
	}  
	memset(c, 0, sizeof *c);  
	c->elemsize = elemsize;  
	c->bufsize = bufsize;  
	c->nbuf = 0;  
	c->buf = (uchar*)(c+1);  
	
	return c; 
}  

總結:
1.每個goroutine可以用一個Task數據結構表示;
2.每創建一個goroutine相當於創建一個Task數據結構並加入到調度隊列中;
3.調度器對於每個Task的調度,主要是基於contextswitch()函數,將當前上下文切換到goroutine的上下文,goroutine執行完之後回到調度器的上下文,繼續執行task的調度;
4.goroutine交還CPU的控制權(即將上下文切換回調度器)的方式有:執行結束、主動讓出、io阻塞;
5.協程的執行是單線程的,可以將這個單線程看作是操作系統,每個協程看作是操作系統的一個進程。進程之間通過操作系統的schuel()調度函數進行切換,goroutine中通過contextswitch()函數進行切換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章