99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Go語言 非阻塞io

2018-07-25 16:08 更新

Go提供的網(wǎng)絡(luò)接口,在用戶層是阻塞的,這樣最符合人們的編程習(xí)慣。在runtime層面,是用epoll/kqueue實(shí)現(xiàn)的非阻塞io,為性能提供了保障。

如何實(shí)現(xiàn)

底層非阻塞io是如何實(shí)現(xiàn)的呢?簡(jiǎn)單地說,所有文件描述符都被設(shè)置成非阻塞的,某個(gè)goroutine進(jìn)行io操作,讀或者寫文件描述符,如果此刻io還沒準(zhǔn)備好,則這個(gè)goroutine會(huì)被放到系統(tǒng)的等待隊(duì)列中,這個(gè)goroutine失去了運(yùn)行權(quán),但并不是真正的整個(gè)系統(tǒng)“阻塞”于系統(tǒng)調(diào)用。

后臺(tái)還有一個(gè)poller會(huì)不停地進(jìn)行poll,所有的文件描述符都被添加到了這個(gè)poller中的,當(dāng)某個(gè)時(shí)刻一個(gè)文件描述符準(zhǔn)備好了,poller就會(huì)喚醒之前因它而阻塞的goroutine,于是goroutine重新運(yùn)行起來。

這個(gè)poller是在后臺(tái)一直運(yùn)行的,前面分析系統(tǒng)調(diào)度章節(jié)時(shí)為了簡(jiǎn)化并沒有提起它。其實(shí)在proc.c文件中,runtime.main函數(shù)的第一行代碼就是

newm(sysmon, nil);

這個(gè)意思就是新建一個(gè)M并讓它運(yùn)行sysmon函數(shù),前面說過M就是機(jī)器的抽象,它會(huì)直接開一個(gè)物理線程。sysmon里面是個(gè)死循環(huán),每睡眠一小會(huì)兒就會(huì)調(diào)用runtime.epoll函數(shù),這個(gè)sysmon就是所謂的poller。

poller是一個(gè)比gc更高優(yōu)先級(jí)的東西,何以見得呢?首先,垃圾回收只是用runtime.newproc建立出來的,它僅僅是個(gè)goroutine任務(wù),而poller是直接用newm建立出來的,它跟startm是平級(jí)的。也就相當(dāng)于gc只是線程池里的任務(wù),而poller自身直接就是worker。然后,gc只是被觸發(fā)性地發(fā)生的,是被動(dòng)的。而poller卻是每隔很短時(shí)間就會(huì)主動(dòng)運(yùn)行。

封裝層次

從最原始的epoll系統(tǒng)調(diào)用,到提供給用戶的網(wǎng)絡(luò)庫函數(shù),可以分成三個(gè)封裝層次。這三個(gè)層次分別是,依賴于系統(tǒng)的api封裝,平臺(tái)獨(dú)立的runtime封裝,提供給用戶的庫的封裝。

最下面一層是依賴于系統(tǒng)部分的封裝。各個(gè)平臺(tái)下的實(shí)現(xiàn)并不一樣,比如linux下是封裝的epoll,freebsd下是封裝的kqueue。以linux為例,實(shí)現(xiàn)了一組調(diào)用epoll相關(guān)系統(tǒng)調(diào)用的封裝:

int32 runtime·epollcreate(int32 size);
int32 runtime·epollcreate1(int32 flags);
int32 runtime·epollctl(int32 epfd, int32 op, int32 fd, EpollEvent *ev);
int32 runtime·epollwait(int32 epfd, EpollEvent *ev, int32 nev, int32 timeout);
void runtime·closeonexec(int32 fd);

它們都是直接使用匯編調(diào)用系統(tǒng)調(diào)用實(shí)現(xiàn)的,比如:

TEXT runtime·epollcreate1(SB),7,$0
    MOVL    8(SP), DI
    MOVL    $291, AX            // syscall entry
    SYSCALL
    RET

這些函數(shù)還要繼續(xù)被封裝成下面一組函數(shù):

runtime·netpollinit(void);
runtime·netpollopen(int32 fd, PollDesc *pd);
runtime·netpollready(G **gpp, PollDesc *pd, int32 mode);

runtime·netpollinit是對(duì)poller進(jìn)行初始化。 runtime·netpollopen是對(duì)fd和pd進(jìn)行關(guān)聯(lián),實(shí)現(xiàn)邊沿觸發(fā)通知。 runtime·netpollready,使用前必須調(diào)用這個(gè)函數(shù)來表示fd是就緒的

不管是哪個(gè)平臺(tái),最終都會(huì)將依賴于系統(tǒng)的部分封裝好,提供上面這樣一組函數(shù)供runtime使用。

接下來是平臺(tái)獨(dú)立的poller的封裝,也就是runtime層的封裝。這一層封裝是最復(fù)雜的,它對(duì)外提供的一組接口是:

func runtime_pollServerInit()
func runtime_pollOpen(fd int) (pd *PollDesc, errno int)
func runtime_pollClose(pd *PollDesc)
func runtime_pollReset(pd *PollDesc, mode int) (err int)
func runtime_pollWait(pd *PollDesc, mode int) (err int)
func runtime_pollSetDeadline(pd *PollDesc, d int64, mode int)
func runtime_pollUnblock(pd *PollDesc)

這一組函數(shù)是由runtime封裝好,提供給net包調(diào)用的。里面定義了一個(gè)PollDesc的結(jié)構(gòu)體,將fd和對(duì)應(yīng)的goroutine封裝起來,從而實(shí)現(xiàn)當(dāng)goroutine讀寫fd阻塞時(shí),將goroutine變?yōu)镚waiting。等一下回頭再看實(shí)現(xiàn)的細(xì)節(jié)。

最后一層封裝層次是提供給用戶的net包。在net包中網(wǎng)絡(luò)文件描述符都是用一個(gè)netFD結(jié)構(gòu)體來表示的,其中有個(gè)成員就是pollDesc。

// 網(wǎng)絡(luò)文件描述符
type netFD struct {
    sysmu  sync.Mutex
    sysref int

    // must lock both sysmu and pollDesc to write
    // can lock either to read
    closing bool

    // immutable until Close
    sysfd       int
    family      int
    sotype      int
    isConnected bool
    sysfile     *os.File
    net         string
    laddr       Addr
    raddr       Addr

    // serialize access to Read and Write methods
    rio, wio sync.Mutex

    // wait server
    pd pollDesc
}

所有用戶的net包的調(diào)用最終調(diào)用到pollDesc的上面那一組函數(shù)中,這樣就實(shí)現(xiàn)了當(dāng)goroutine讀或?qū)懽枞麜r(shí)會(huì)被放到等待隊(duì)列。最終的效果就是用戶層阻塞,底層非阻塞。

文件描述符和goroutine

當(dāng)一個(gè)goroutine進(jìn)行io阻塞時(shí),會(huì)去被放到等待隊(duì)列。這里面就關(guān)鍵的就是建立起文件描述符和goroutine之間的關(guān)聯(lián)。pollDesc結(jié)構(gòu)體就是完成這個(gè)任務(wù)的。它的結(jié)構(gòu)體定義如下:

struct PollDesc
{
    PollDesc* link;    // in pollcache, protected by pollcache.Lock
    Lock;        // protectes the following fields
    int32    fd;
    bool    closing;
    uintptr    seq;    // protects from stale timers and ready notifications
    G*    rg;    // 因讀這個(gè)fd而阻塞的G,等待READY信號(hào)
    Timer    rt;    // read deadline timer (set if rt.fv != nil)
    int64    rd;    // read deadline
    G*    wg;    // 因?qū)戇@個(gè)fd而阻塞的goroutines
    Timer    wt;
    int64    wd;
};

這個(gè)結(jié)構(gòu)體是重用的,其中l(wèi)ink就是將它鏈起來。PollDesc對(duì)象必須是類型穩(wěn)定的,因?yàn)樵诿枋龇P(guān)閉/重用之后我們會(huì)得到epoll/kqueue就緒通知。結(jié)構(gòu)體中有一個(gè)seq序號(hào),穩(wěn)定的通知是通過使用這個(gè)序號(hào)實(shí)現(xiàn)的,當(dāng)deadline改變或者描述符重用時(shí),序號(hào)會(huì)增加。

runtime_pollServerInit的實(shí)現(xiàn)就是調(diào)用更下層的runtime·netpollinit函數(shù)。 runtime_pollOpen從PollDesc結(jié)構(gòu)體緩存中拿一個(gè)出來,設(shè)置好它的fd。之所以叫Open而不是new,就是因?yàn)镻ollDesc結(jié)構(gòu)體是重用的。 runtime_pollClose函數(shù)調(diào)用runtime·netpollclose后將PollDesc結(jié)構(gòu)體放回緩存。

這些都還沒涉及到fd與goroutine交互部分,僅僅是直接對(duì)epoll的調(diào)用。從下面這個(gè)函數(shù)可以看到fd與goroutine交互部分:

func runtime_pollWait(pd *PollDesc, mode int) (err int)

它會(huì)調(diào)用到netpollblock,這個(gè)函數(shù)是這樣子的:

static void
netpollblock(PollDesc *pd, int32 mode)
{
    G **gpp;

    gpp = &pd->rg;
    if(mode == 'w')
        gpp = &pd->wg;
    if(*gpp == READY) {
        *gpp = nil;
        return;
    }
    if(*gpp != nil)
        runtime·throw("epoll: double wait");
    *gpp = g;
    runtime·park(runtime·unlock, &pd->Lock, "IO wait");
    runtime·lock(pd);
}

最后的runtime.park函數(shù),就是將當(dāng)前的goroutine(調(diào)用者)設(shè)置為waiting狀態(tài)。

上面這一部分是goroutine被放到等待隊(duì)列的部分,下面看它被喚醒的部分。在sysmon函數(shù)中,會(huì)不停地調(diào)用runtime.epoll,這個(gè)函數(shù)對(duì)就緒的網(wǎng)絡(luò)連接進(jìn)行poll,返回可運(yùn)行的goroutine。epoll只能知道哪個(gè)fd就緒了,那么它怎么知道哪個(gè)goroutine就緒了呢?原來epoll的data域存放的就是PollDesc結(jié)構(gòu)體指針。因此就可以得到其中的goroutine了。


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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)