這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
chan是一個FIFO隊列,chan分成兩種類型同步和非同步
同步的chan完成寄件者和接受者之間手遞手傳遞元素的過程,必須要求對方的存在才能完成一次發送或接受
非同步chan發送和接受都是基於chan的緩衝,但當緩衝隊列填滿後,寄件者就會進入發送隊列, 當緩衝隊列為空白時,接受者就會接入等待隊列。
chan的資料結構:
structHchan{ uintgoqcount;// total data in the q uintgodataqsiz;// size of the circular q uint16elemsize; uint16pad;// ensures proper alignment of the buffer that follows Hchan in memory boolclosed; Alg*elemalg;// interface for element type uintgosendx;// send index uintgorecvx;// receive index WaitQrecvq;// list of recv waiters WaitQsendq;// list of send waiters Lock;};
chan發送
voidruntime·chansend(ChanType *t, Hchan *c, byte *ep, bool *pres, void *pc){ SudoG *sg; SudoG mysg; G* gp; int64 t0; if(c == nil) { USED(t); if(pres != nil) { *pres = false; return; } runtime·park(nil, nil, "chan send (nil chan)"); return; // not reached } if(debug) { runtime·printf("chansend: chan=%p; elem=", c); c->elemalg->print(c->elemsize, ep); runtime·prints("\n"); } t0 = 0; mysg.releasetime = 0; if(runtime·blockprofilerate > 0) { t0 = runtime·cputicks(); mysg.releasetime = -1; } runtime·lock(c); if(raceenabled) runtime·racereadpc(c, pc, runtime·chansend); if(c->closed) goto closed; if(c->dataqsiz > 0) goto asynch; sg = dequeue(&c->recvq); if(sg != nil) { if(raceenabled) racesync(c, sg); runtime·unlock(c); gp = sg->g; gp->param = sg; if(sg->elem != nil) c->elemalg->copy(c->elemsize, sg->elem, ep); if(sg->releasetime) sg->releasetime = runtime·cputicks(); runtime·ready(gp); if(pres != nil) *pres = true; return; } if(pres != nil) { runtime·unlock(c); *pres = false; return; } mysg.elem = ep; mysg.g = g; mysg.selgen = NOSELGEN; g->param = nil; enqueue(&c->sendq, &mysg); runtime·park(runtime·unlock, c, "chan send"); if(g->param == nil) { runtime·lock(c); if(!c->closed) runtime·throw("chansend: spurious wakeup"); goto closed; } if(mysg.releasetime > 0) runtime·blockevent(mysg.releasetime - t0, 2); return; asynch: if(c->closed) goto closed; if(c->qcount >= c->dataqsiz) { if(pres != nil) { runtime·unlock(c); *pres = false; return; } mysg.g = g; mysg.elem = nil; mysg.selgen = NOSELGEN; enqueue(&c->sendq, &mysg); runtime·park(runtime·unlock, c, "chan send"); runtime·lock(c); goto asynch; } if(raceenabled) runtime·racerelease(chanbuf(c, c->sendx)); c->elemalg->copy(c->elemsize, chanbuf(c, c->sendx), ep); if(++c->sendx == c->dataqsiz) c->sendx = 0; c->qcount++; sg = dequeue(&c->recvq); if(sg != nil) { gp = sg->g; runtime·unlock(c); if(sg->releasetime) sg->releasetime = runtime·cputicks(); runtime·ready(gp); } else runtime·unlock(c); if(pres != nil) *pres = true; if(mysg.releasetime > 0) runtime·blockevent(mysg.releasetime - t0, 2); return; closed: runtime·unlock(c); runtime·panicstring("send on closed channel");}
- 判斷隊列類型,非同步隊列則轉到5
- 從等待隊列中擷取等待隊列中的接受者
- 如果取到接受者,則將對象直接傳遞給接受者,然後喚醒接受者,發送過程完成
- 如果未取到接受者,則將寄件者enqueue到發送隊列,寄件者進入阻塞狀態
- 非同步隊列首先判斷隊列緩衝是否還有空間
- 如果緩衝空間已滿,則將寄件者enqueue到發送隊列,寄件者進入阻塞狀態
- 如果緩衝空間未滿,則將元素copy到緩衝中,這時寄件者就不會進入阻塞狀態
- 嘗試喚醒等待隊列中的一個接受者
chan接受
voidruntime·chanrecv(ChanType *t, Hchan* c, byte *ep, bool *selected, bool *received){ SudoG *sg; SudoG mysg; G *gp; int64 t0; if(debug) runtime·printf("chanrecv: chan=%p\n", c); if(c == nil) { USED(t); if(selected != nil) { *selected = false; return; } runtime·park(nil, nil, "chan receive (nil chan)"); return; // not reached } t0 = 0; mysg.releasetime = 0; if(runtime·blockprofilerate > 0) { t0 = runtime·cputicks(); mysg.releasetime = -1; } runtime·lock(c); if(c->dataqsiz > 0) goto asynch; if(c->closed) goto closed; sg = dequeue(&c->sendq); if(sg != nil) { if(raceenabled) racesync(c, sg); runtime·unlock(c); if(ep != nil) c->elemalg->copy(c->elemsize, ep, sg->elem); gp = sg->g; gp->param = sg; if(sg->releasetime) sg->releasetime = runtime·cputicks(); runtime·ready(gp); if(selected != nil) *selected = true; if(received != nil) *received = true; return; } if(selected != nil) { runtime·unlock(c); *selected = false; return; } mysg.elem = ep; mysg.g = g; mysg.selgen = NOSELGEN; g->param = nil; enqueue(&c->recvq, &mysg); runtime·park(runtime·unlock, c, "chan receive"); if(g->param == nil) { runtime·lock(c); if(!c->closed) runtime·throw("chanrecv: spurious wakeup"); goto closed; } if(received != nil) *received = true; if(mysg.releasetime > 0) runtime·blockevent(mysg.releasetime - t0, 2); return; asynch: if(c->qcount <= 0) { if(c->closed) goto closed; if(selected != nil) { runtime·unlock(c); *selected = false; if(received != nil) *received = false; return; } mysg.g = g; mysg.elem = nil; mysg.selgen = NOSELGEN; enqueue(&c->recvq, &mysg); runtime·park(runtime·unlock, c, "chan receive"); runtime·lock(c); goto asynch; } if(raceenabled) runtime·raceacquire(chanbuf(c, c->recvx)); if(ep != nil) c->elemalg->copy(c->elemsize, ep, chanbuf(c, c->recvx)); c->elemalg->copy(c->elemsize, chanbuf(c, c->recvx), nil); if(++c->recvx == c->dataqsiz) c->recvx = 0; c->qcount--; sg = dequeue(&c->sendq); if(sg != nil) { gp = sg->g; runtime·unlock(c); if(sg->releasetime) sg->releasetime = runtime·cputicks(); runtime·ready(gp); } else runtime·unlock(c); if(selected != nil) *selected = true; if(received != nil) *received = true; if(mysg.releasetime > 0) runtime·blockevent(mysg.releasetime - t0, 2); return; closed: if(ep != nil) c->elemalg->copy(c->elemsize, ep, nil); if(selected != nil) *selected = true; if(received != nil) *received = false; if(raceenabled) runtime·raceacquire(c); runtime·unlock(c); if(mysg.releasetime > 0) runtime·blockevent(mysg.releasetime - t0, 2);}
- 判斷隊列類型,如果是非同步隊列則轉到5
- 從發送隊列擷取接受者
- 如果取到接受者,則直接從接受者擷取元素,並喚醒寄件者,接受過程完成
- 如果未取到接受者,則將自身enqueue到等待隊列,阻塞goroutine等待寄件者喚醒
- 非同步隊列首先判斷隊列緩衝中是否有元素
- 緩衝為空白時,則將自身enqueue到等待隊列,阻塞goroutine等待寄件者喚醒
- 緩衝非空時,取出緩衝中的第一個元素
- 然後嘗試喚醒發送隊列中的一個寄件者