国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Go語(yǔ)言 channel介紹

2018-07-25 14:44 更新

channel數(shù)據(jù)結(jié)構(gòu)

Go語(yǔ)言channel是first-class的,意味著它可以被存儲(chǔ)到變量中,可以作為參數(shù)傳遞給函數(shù),也可以作為函數(shù)的返回值返回。作為Go語(yǔ)言的核心特征之一,雖然channel看上去很高端,但是其實(shí)channel僅僅就是一個(gè)數(shù)據(jù)結(jié)構(gòu)而已,結(jié)構(gòu)體定義如下:

struct    Hchan
{
    uintgo    qcount;            // 隊(duì)列q中的總數(shù)據(jù)數(shù)量
    uintgo    dataqsiz;        // 環(huán)形隊(duì)列q的數(shù)據(jù)大小
    uint16    elemsize;
    bool    closed;
    uint8    elemalign;
    Alg*    elemalg;        // interface for element type
    uintgo    sendx;            // 發(fā)送index
    uintgo    recvx;            // 接收index
    WaitQ    recvq;            // 因recv而阻塞的等待隊(duì)列
    WaitQ    sendq;            // 因send而阻塞的等待隊(duì)列
    Lock;
};

讓我們來(lái)看一個(gè)Hchan這個(gè)結(jié)構(gòu)體。其中一個(gè)核心的部分是存放channel數(shù)據(jù)的環(huán)形隊(duì)列,由qcount和elemsize分別指定了隊(duì)列的容量和當(dāng)前使用量。dataqsize是隊(duì)列的大小。elemalg是元素操作的一個(gè)Alg結(jié)構(gòu)體,記錄下元素的操作,如copy函數(shù),equal函數(shù),hash函數(shù)等。

可能會(huì)有人疑惑,結(jié)構(gòu)體中只看到了隊(duì)列大小相關(guān)的域,并沒(méi)有看到存放數(shù)據(jù)的域???如果是帶緩沖區(qū)的chan,則緩沖區(qū)數(shù)據(jù)實(shí)際上是緊接著Hchan結(jié)構(gòu)體中分配的。

c = (Hchan*)runtime.mal(n + hint*elem->size);

另一個(gè)重要部分就是recvq和sendq兩個(gè)鏈表,一個(gè)是因讀這個(gè)通道而導(dǎo)致阻塞的goroutine,另一個(gè)是因?yàn)閷?xiě)這個(gè)通道而阻塞的goroutine。如果一個(gè)goroutine阻塞于channel了,那么它就被掛在recvq或sendq中。WaitQ是鏈表的定義,包含一個(gè)頭結(jié)點(diǎn)和一個(gè)尾結(jié)點(diǎn):

struct    WaitQ
{
    SudoG*    first;
    SudoG*    last;
};

隊(duì)列中的每個(gè)成員是一個(gè)SudoG結(jié)構(gòu)體變量。

struct    SudoG
{
    G*    g;        // g and selgen constitute
    uint32    selgen;        // a weak pointer to g
    SudoG*    link;
    int64    releasetime;
    byte*    elem;        // data element
};

該結(jié)構(gòu)中主要的就是一個(gè)g和一個(gè)elem。elem用于存儲(chǔ)goroutine的數(shù)據(jù)。讀通道時(shí),數(shù)據(jù)會(huì)從Hchan的隊(duì)列中拷貝到SudoG的elem域。寫(xiě)通道時(shí),數(shù)據(jù)則是由SudoG的elem域拷貝到Hchan的隊(duì)列中。

Hchan結(jié)構(gòu)如下圖所示: 

讀寫(xiě)channel操作

先看寫(xiě)channel的操作,基本的寫(xiě)channel操作,在底層運(yùn)行時(shí)庫(kù)中對(duì)應(yīng)的是一個(gè)runtime.chansend函數(shù)。

c <- v

在運(yùn)行時(shí)庫(kù)中會(huì)執(zhí)行:

void runtime·chansend(ChanType *t, Hchan *c, byte *ep, bool *pres, void *pc)

其中c就是channel,ep是取變量v的地址。這里的傳值約定是調(diào)用者負(fù)責(zé)分配好ep的空間,僅需要簡(jiǎn)單的取變量地址就夠了。pres參數(shù)是在select中的通道操作使用的。

這個(gè)函數(shù)首先會(huì)區(qū)分是同步還是異步。同步是指chan是不帶緩沖區(qū)的,因此可能寫(xiě)阻塞,而異步是指chan帶緩沖區(qū),只有緩沖區(qū)滿(mǎn)才阻塞。

在同步的情況下,由于channel本身是不帶數(shù)據(jù)緩存的,這時(shí)首先會(huì)查看Hchan結(jié)構(gòu)體中的recvq鏈表時(shí)否為空,即是否有因?yàn)樽x該管道而阻塞的goroutine。如果有則可以正常寫(xiě)channel,否則操作會(huì)阻塞。

recvq不為空的情況下,將一個(gè)SudoG結(jié)構(gòu)體出隊(duì)列,將傳給通道的數(shù)據(jù)(函數(shù)參數(shù)ep)拷貝到SudoG結(jié)構(gòu)體中的elem域,并將SudoG中的g放到就緒隊(duì)列中,狀態(tài)置為ready,然后函數(shù)返回。

如果recvq為空,否則要將當(dāng)前goroutine阻塞。此時(shí)將一個(gè)SudoG結(jié)構(gòu)體,掛到通道的sendq鏈表中,這個(gè)SudoG中的elem域是參數(shù)eq,SudoG中的g是當(dāng)前的goroutine。當(dāng)前goroutine會(huì)被設(shè)置為waiting狀態(tài)并掛到等待隊(duì)列中。

在異步的情況,如果緩沖區(qū)滿(mǎn)了,也是要將當(dāng)前goroutine和數(shù)據(jù)一起作為SudoG結(jié)構(gòu)體掛在sendq隊(duì)列中,表示因?qū)慶hannel而阻塞。否則也是先看有沒(méi)有recvq鏈表是否為空,有就喚醒。

跟同步不同的是在channel緩沖區(qū)不滿(mǎn)的情況,這里不會(huì)阻塞寫(xiě)者,而是將數(shù)據(jù)放到channel的緩沖區(qū)中,調(diào)用者返回。

讀channel的操作也是類(lèi)似的,對(duì)應(yīng)的函數(shù)是runtime.chansend。一個(gè)是收一個(gè)是發(fā),基本的過(guò)程都是差不多的。

需要注意的是幾種特殊情況下的通道操作--空通道和關(guān)閉的通道。

空通道是指將一個(gè)channel賦值為nil,或者定義后不調(diào)用make進(jìn)行初始化。按照Go語(yǔ)言的語(yǔ)言規(guī)范,讀寫(xiě)空通道是永遠(yuǎn)阻塞的。其實(shí)在函數(shù)runtime.chansend和runtime.chanrecv開(kāi)頭就有判斷這類(lèi)情況,如果發(fā)現(xiàn)參數(shù)c是空的,則直接將當(dāng)前的goroutine放到等待隊(duì)列,狀態(tài)設(shè)置為waiting。

讀一個(gè)關(guān)閉的通道,永遠(yuǎn)不會(huì)阻塞,會(huì)返回一個(gè)通道數(shù)據(jù)類(lèi)型的零值。這個(gè)實(shí)現(xiàn)也很簡(jiǎn)單,將零值復(fù)制到調(diào)用函數(shù)的參數(shù)ep中。寫(xiě)一個(gè)關(guān)閉的通道,則會(huì)panic。關(guān)閉一個(gè)空通道,也會(huì)導(dǎo)致panic。

select的實(shí)現(xiàn)

select-case中的chan操作編譯成了if-else。比如:

select {
case v = <-c:
        ...foo
default:
        ...bar
}

會(huì)被編譯為:

if selectnbrecv(&v, c) {
        ...foo
} else {
        ...bar
}

類(lèi)似地

select {
case v, ok = <-c:
    ... foo
default:
    ... bar
}

會(huì)被編譯為:

if c != nil && selectnbrecv2(&v, &ok, c) {
    ... foo
} else {
    ... bar
}

接下來(lái)就是看一下selectnbrecv相關(guān)的函數(shù)了。其實(shí)沒(méi)有任何特殊的魔法,這些函數(shù)只是簡(jiǎn)單地調(diào)用runtime.chanrecv函數(shù),只不過(guò)設(shè)置了一個(gè)參數(shù),告訴當(dāng)runtime.chanrecv函數(shù),當(dāng)不能完成操作時(shí)不要阻塞,而是返回失敗。也就是說(shuō),所有的select操作其實(shí)都僅僅是被換成了if-else判斷,底層調(diào)用的不阻塞的通道操作函數(shù)。

在Go的語(yǔ)言規(guī)范中,select中的case的執(zhí)行順序是隨機(jī)的,而不像switch中的case那樣一條一條的順序執(zhí)行。那么,如何實(shí)現(xiàn)隨機(jī)呢?

select和case關(guān)鍵字使用了下面的結(jié)構(gòu)體:

struct    Scase
{
    SudoG    sg;            // must be first member (cast to Scase)
    Hchan*    chan;        // chan
    byte*    pc;            // return pc
    uint16    kind;
    uint16    so;            // vararg of selected bool
    bool*    receivedp;    // pointer to received bool (recv2)
};

struct    Select
{
    uint16    tcase;            // 總的scase[]數(shù)量
    uint16    ncase;            // 當(dāng)前填充了的scase[]數(shù)量
    uint16*    pollorder;        // case的poll次序
    Hchan**    lockorder;        // channel的鎖住的次序
    Scase    scase[1];        // 每個(gè)case會(huì)在結(jié)構(gòu)體里有一個(gè)Scase,順序是按出現(xiàn)的次序
};

每個(gè)select都對(duì)應(yīng)一個(gè)Select結(jié)構(gòu)體。在Select數(shù)據(jù)結(jié)構(gòu)中有個(gè)Scase數(shù)組,記錄下了每一個(gè)case,而Scase中包含了Hchan。然后pollorder數(shù)組將元素隨機(jī)排列,這樣就可以將Scase亂序了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)