欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go底層channel實(shí)現(xiàn)原理及示例詳解

 更新時間:2022年08月08日 10:58:51   作者:阿甘與阿Q  
這篇文章主要介紹了Go底層channel實(shí)現(xiàn)原理及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

概念:

Go中的channel 是一個隊列,遵循先進(jìn)先出的原則,負(fù)責(zé)協(xié)程之間的通信(Go 語言提倡不要通過共享內(nèi)存來通信,而要通過通信來實(shí)現(xiàn)內(nèi)存共享,CSP(Communicating Sequential Process)并發(fā)模型,就是通過 goroutine 和 channel 來實(shí)現(xiàn)的)

使用場景:

停止信號監(jiān)聽

定時任務(wù)

生產(chǎn)方和消費(fèi)方解耦

控制并發(fā)數(shù)

底層數(shù)據(jù)結(jié)構(gòu):

通過var聲明或者make函數(shù)創(chuàng)建的channel變量是一個存儲在函數(shù)棧幀上的指針,占用8個字節(jié),指向堆上的hchan結(jié)構(gòu)體

源碼包中src/runtime/chan.go定義了hchan的數(shù)據(jù)結(jié)構(gòu):

hchan結(jié)構(gòu)體:

type hchan struct {
 closed   uint32   // channel是否關(guān)閉的標(biāo)志
 elemtype *_type   // channel中的元素類型
 // channel分為無緩沖和有緩沖兩種。
 // 對于有緩沖的channel存儲數(shù)據(jù),使用了 ring buffer(環(huán)形緩沖區(qū)) 來緩存寫入的數(shù)據(jù),本質(zhì)是循環(huán)數(shù)組
 // 為啥是循環(huán)數(shù)組?普通數(shù)組不行嗎,普通數(shù)組容量固定更適合指定的空間,彈出元素時,普通數(shù)組需要全部都前移
 // 當(dāng)下標(biāo)超過數(shù)組容量后會回到第一個位置,所以需要有兩個字段記錄當(dāng)前讀和寫的下標(biāo)位置
 buf      unsafe.Pointer // 指向底層循環(huán)數(shù)組的指針(環(huán)形緩沖區(qū))
 qcount   uint           // 循環(huán)數(shù)組中的元素數(shù)量
 dataqsiz uint           // 循環(huán)數(shù)組的長度
 elemsize uint16                 // 元素的大小
 sendx    uint           // 下一次寫下標(biāo)的位置
 recvx    uint           // 下一次讀下標(biāo)的位置
 // 嘗試讀取channel或向channel寫入數(shù)據(jù)而被阻塞的goroutine
 recvq    waitq  // 讀等待隊列
 sendq    waitq  // 寫等待隊列
 lock mutex //互斥鎖,保證讀寫channel時不存在并發(fā)競爭問題
}

等待隊列:

雙向鏈表,包含一個頭結(jié)點(diǎn)和一個尾結(jié)點(diǎn)

每個節(jié)點(diǎn)是一個sudog結(jié)構(gòu)體變量,記錄哪個協(xié)程在等待,等待的是哪個channel,等待發(fā)送/接收的數(shù)據(jù)在哪里

type waitq struct {
   first *sudog
   last  *sudog
}
type sudog struct {
    g *g
    next *sudog
    prev *sudog
    elem unsafe.Pointer 
    c        *hchan 
    ...
}

操作:

創(chuàng)建

使用 make(chan T, cap) 來創(chuàng)建 channel,make 語法會在編譯時,轉(zhuǎn)換為 makechan64 和 makechan

func makechan64(t *chantype, size int64) *hchan {
    if int64(int(size)) != size {
        panic(plainError("makechan: size out of range"))
    }
    return makechan(t, int(size))
}

創(chuàng)建channel 有兩種,一種是帶緩沖的channel,一種是不帶緩沖的channel

// 帶緩沖
ch := make(chan int, 3)
// 不帶緩沖
ch := make(chan int)

創(chuàng)建時會做一些檢查:

  • 元素大小不能超過 64K
  • 元素的對齊大小不能超過 maxAlign 也就是 8 字節(jié)
  • 計算出來的內(nèi)存是否超過限制

創(chuàng)建時的策略:

  • 如果是無緩沖的 channel,會直接給 hchan 分配內(nèi)存
  • 如果是有緩沖的 channel,并且元素不包含指針,那么會為 hchan 和底層數(shù)組分配一段連續(xù)的地址
  • 如果是有緩沖的 channel,并且元素包含指針,那么會為 hchan 和底層數(shù)組分別分配地址

發(fā)送

發(fā)送操作,編譯時轉(zhuǎn)換為runtime.chansend函數(shù)

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool 

阻塞式:

調(diào)用chansend函數(shù),并且block=true

ch <- 10

非阻塞式:

調(diào)用chansend函數(shù),并且block=false

select {
    case ch <- 10:
    ...
  default
}

向 channel 中發(fā)送數(shù)據(jù)時大概分為兩大塊:檢查和數(shù)據(jù)發(fā)送,數(shù)據(jù)發(fā)送流程如下:

如果 channel 的讀等待隊列存在接收者goroutine

  • 將數(shù)據(jù)直接發(fā)送給第一個等待的 goroutine, 喚醒接收的 goroutine

如果 channel 的讀等待隊列不存在接收者goroutine

  • 如果循環(huán)數(shù)組buf未滿,那么將會把數(shù)據(jù)發(fā)送到循環(huán)數(shù)組buf的隊尾
  • 如果循環(huán)數(shù)組buf已滿,這個時候就會走阻塞發(fā)送的流程,將當(dāng)前 goroutine 加入寫等待隊列,并掛起等待喚醒

接收

發(fā)送操作,編譯時轉(zhuǎn)換為runtime.chanrecv函數(shù)

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) 

阻塞式:

調(diào)用chanrecv函數(shù),并且block=true

<ch
v := <ch
v, ok := <ch
// 當(dāng)channel關(guān)閉時,for循環(huán)會自動退出,無需主動監(jiān)測channel是否關(guān)閉,可以防止讀取已經(jīng)關(guān)閉的channel,造成讀到數(shù)據(jù)為通道所存儲的數(shù)據(jù)類型的零值
for i := range ch {
    fmt.Println(i)
}

非阻塞式:

調(diào)用chanrecv函數(shù),并且block=false

select {
    case <-ch:
    ...
  default
}

向 channel 中接收數(shù)據(jù)時大概分為兩大塊,檢查和數(shù)據(jù)發(fā)送,而數(shù)據(jù)接收流程如下:

如果 channel 的寫等待隊列存在發(fā)送者goroutine

  • 如果是無緩沖 channel,直接從第一個發(fā)送者goroutine那里把數(shù)據(jù)拷貝給接收變量,喚醒發(fā)送的 goroutine
  • 如果是有緩沖 channel(已滿),將循環(huán)數(shù)組buf的隊首元素拷貝給接收變量,將第一個發(fā)送者goroutine的數(shù)據(jù)拷貝到 buf循環(huán)數(shù)組隊尾,喚醒發(fā)送的 goroutine

如果 channel 的寫等待隊列不存在發(fā)送者goroutine

  • 如果循環(huán)數(shù)組buf非空,將循環(huán)數(shù)組buf的隊首元素拷貝給接收變量
  • 如果循環(huán)數(shù)組buf為空,這個時候就會走阻塞接收的流程,將當(dāng)前 goroutine 加入讀等待隊列,并掛起等待喚醒

關(guān)閉

關(guān)閉操作,調(diào)用close函數(shù),編譯時轉(zhuǎn)換為runtime.closechan函數(shù)

close(ch)
func closechan(c *hchan) 

案例分析:

package main
import (
    "fmt"
    "time"
    "unsafe"
)
func main() {
  // ch是長度為4的帶緩沖的channel
  // 初始hchan結(jié)構(gòu)體重的buf為空,sendx和recvx均為0
    ch := make(chan string, 4)
    fmt.Println(ch, unsafe.Sizeof(ch))
    go sendTask(ch)
    go receiveTask(ch)
    time.Sleep(1 * time.Second)
}
// G1是發(fā)送者
// 當(dāng)G1向ch里發(fā)送數(shù)據(jù)時,首先會對buf加鎖,然后將task存儲的數(shù)據(jù)copy到buf中,然后sendx++,然后釋放對buf的鎖
func sendTask(ch chan string) {
    taskList := []string{"this", "is", "a", "demo"}
    for _, task := range taskList {
        ch <- task //發(fā)送任務(wù)到channel
    }
}
// G2是接收者
// 當(dāng)G2消費(fèi)ch的時候,會首先對buf加鎖,然后將buf中的數(shù)據(jù)copy到task變量對應(yīng)的內(nèi)存里,然后recvx++,并釋放鎖
func receiveTask(ch chan string) {
    for {
        task := <-ch                  //接收任務(wù)
        fmt.Println("received", task) //處理任務(wù)
    }
}

總結(jié)hchan結(jié)構(gòu)體的主要組成部分有四個:

  • 用來保存goroutine之間傳遞數(shù)據(jù)的循環(huán)數(shù)組:buf
  • 用來記錄此循環(huán)數(shù)組當(dāng)前發(fā)送或接收數(shù)據(jù)的下標(biāo)值:sendx和recvx
  • 用于保存向該chan發(fā)送和從該chan接收數(shù)據(jù)被阻塞的goroutine隊列: sendq 和 recvq
  • 保證channel寫入和讀取數(shù)據(jù)時線程安全的鎖:lock

以上就是Go底層channel實(shí)現(xiàn)原理及示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go channel底層原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang使用net/rpc庫實(shí)現(xiàn)rpc

    golang使用net/rpc庫實(shí)現(xiàn)rpc

    這篇文章主要為大家詳細(xì)介紹了golang如何使用net/rpc庫實(shí)現(xiàn)rpc,文章的示例代碼講解詳細(xì),具有一定的借鑒價值,需要的小伙伴可以參考一下
    2024-01-01
  • golang連接mysql數(shù)據(jù)庫操作使用示例

    golang連接mysql數(shù)據(jù)庫操作使用示例

    這篇文章主要為大家介紹了golang連接mysql數(shù)據(jù)庫操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • go語言環(huán)境變量設(shè)置全過程

    go語言環(huán)境變量設(shè)置全過程

    這篇文章主要介紹了go語言環(huán)境變量設(shè)置全過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Golang跳轉(zhuǎn)語句continue與goto使用語法詳解

    Golang跳轉(zhuǎn)語句continue與goto使用語法詳解

    這篇文章主要介紹了Golang跳轉(zhuǎn)語句continue與goto使用語法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-01-01
  • 從入門到精通:Go語言XML數(shù)據(jù)解析指南

    從入門到精通:Go語言XML數(shù)據(jù)解析指南

    Go語言的XML包提供了強(qiáng)大的數(shù)據(jù)解析功能,讓你輕松處理各種XML格式的數(shù)據(jù),這個指南將帶你深入了解如何使用Go語言的XML包,快速上手XML數(shù)據(jù)解析,準(zhǔn)備好開啟XML解析之旅了嗎?Let's?Go!
    2024-03-03
  • 一站式解決方案:在Windows和Linux上快速搭建Go語言開發(fā)環(huán)境

    一站式解決方案:在Windows和Linux上快速搭建Go語言開發(fā)環(huán)境

    本文將介紹如何在Windows和Linux操作系統(tǒng)下搭建Go語言開發(fā)環(huán)境,以幫助您更高效地進(jìn)行Go語言開發(fā),需要的朋友可以參考下
    2023-10-10
  • Go map底層實(shí)現(xiàn)與擴(kuò)容規(guī)則和特性分類詳細(xì)講解

    Go map底層實(shí)現(xiàn)與擴(kuò)容規(guī)則和特性分類詳細(xì)講解

    這篇文章主要介紹了Go map底層實(shí)現(xiàn)與擴(kuò)容規(guī)則和特性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-03-03
  • Go Java算法之同構(gòu)字符串示例詳解

    Go Java算法之同構(gòu)字符串示例詳解

    這篇文章主要為大家介紹了Go Java算法之同構(gòu)字符串示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang中strconv.ParseInt函數(shù)用法示例

    golang中strconv.ParseInt函數(shù)用法示例

    這篇文章主要介紹了golang中strconv.ParseInt函數(shù)用法,實(shí)例分析了strconv.ParseInt函數(shù)將字符串轉(zhuǎn)換為數(shù)字的簡單使用方法,需要的朋友可以參考下
    2016-07-07
  • Go語言異常處理(Panic和recovering)用法詳解

    Go語言異常處理(Panic和recovering)用法詳解

    異常處理是程序健壯性的關(guān)鍵,往往開發(fā)人員的開發(fā)經(jīng)驗的多少從異常部分處理上就能得到體現(xiàn)。Go語言中沒有Try?Catch?Exception機(jī)制,但是提供了panic-and-recover機(jī)制,本文就來詳細(xì)講講他們的用法
    2022-07-07

最新評論