協程:
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()函數進行切換。