最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

概念:

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

使用場景:

停止信號監(jiān)聽

定時任務(wù)

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

控制并發(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é)點和一個尾結(jié)點

每個節(jié)點是一個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消費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實現(xiàn)原理及示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go channel底層原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 在Go中使用jwt的教程詳解

    在Go中使用jwt的教程詳解

    JWT (JSON Web Tokens) 是一種基于 JSON 格式的輕量級身份驗證和授權(quán)方案,用于在各方之間以JSON方式安全地傳輸信息,本文給大家詳細(xì)介紹了在Go中使用jwt的教程,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • 關(guān)于go-micro與其它gRPC框架之間的通信問題及解決方法

    關(guān)于go-micro與其它gRPC框架之間的通信問題及解決方法

    在之前的文章中分別介紹了使用gRPC官方插件和go-micro插件開發(fā)gRPC應(yīng)用程序的方式,都能正常走通。不過當(dāng)兩者混合使用的時候,互相訪問就成了問題,下面通過本文給大家講解下go-micro與gRPC框架通信問題,一起看看吧
    2022-04-04
  • 一文帶你了解Go語言中的單元測試

    一文帶你了解Go語言中的單元測試

    寫過單元測試的開發(fā)人員應(yīng)該理解,單元測試最核心的價值是為了證明:為什么我寫的代碼是正確的?也就是從邏輯角度幫你檢查你的代碼。本文就來和大家詳細(xì)聊聊Go語言中的單元測試,需要的可以參考一下
    2022-07-07
  • go語言使用io和bufio包進行流操作示例詳解

    go語言使用io和bufio包進行流操作示例詳解

    這篇文章主要為大家介紹了go語言使用io和bufio包進行流操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • go語言中sort包的實現(xiàn)方法與應(yīng)用詳解

    go語言中sort包的實現(xiàn)方法與應(yīng)用詳解

    golang中也實現(xiàn)了排序算法的包sort包,所以下面這篇文章主要給大家介紹了關(guān)于go語言中sort包的實現(xiàn)方法與應(yīng)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • golang連接MongoDB數(shù)據(jù)庫及數(shù)據(jù)庫操作指南

    golang連接MongoDB數(shù)據(jù)庫及數(shù)據(jù)庫操作指南

    MongoDB是Nosql中常用的一種數(shù)據(jù)庫,下面這篇文章主要給大家介紹了關(guān)于golang連接MongoDB數(shù)據(jù)庫及數(shù)據(jù)庫操作的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • Go設(shè)計模式之觀察者模式圖解

    Go設(shè)計模式之觀察者模式圖解

    觀察者模式是一種行為設(shè)計模式, 允許你定義一種訂閱機制, 可在對象事件發(fā)生時通知多個 “觀察” 該對象的其他對象,下面這篇文章主要給大家介紹了關(guān)于圖解Go觀察者模式的相關(guān)資料,需要的朋友可以參考下
    2023-07-07
  • Golang使用minio替代文件系統(tǒng)的實戰(zhàn)教程

    Golang使用minio替代文件系統(tǒng)的實戰(zhàn)教程

    本文討論項目開發(fā)中直接文件系統(tǒng)的限制或不足,接著介紹Minio對象存儲的優(yōu)勢,同時給出Golang的實際示例代碼,包括初始化客戶端、讀取minio對象以及設(shè)置過期策略等,需要的朋友可以參考下
    2025-01-01
  • golang之資源釋放/異常錯誤處理解析

    golang之資源釋放/異常錯誤處理解析

    這篇文章主要為大家介紹了golang之資源釋放/異常錯誤處理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • VS Code配置Go語言開發(fā)環(huán)境的詳細(xì)教程

    VS Code配置Go語言開發(fā)環(huán)境的詳細(xì)教程

    這篇文章主要介紹了VS Code配置Go語言開發(fā)環(huán)境的詳細(xì)教程,本文通過實例代碼圖文相結(jié)合的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05

最新評論

博野县| 桐乡市| 驻马店市| 紫金县| 南安市| 广安市| 洛隆县| 榕江县| 石棉县| 巴楚县| 洛浦县| 兴义市| 麻城市| 辽中县| 威信县| 宜昌市| 辽阳县| 印江| 枣庄市| 乳源| 富裕县| 苍梧县| 尉氏县| 临西县| 罗田县| 达拉特旗| 宝山区| 雷州市| 阿图什市| 海丰县| 双牌县| 连平县| 应用必备| 法库县| 潮安县| 麻栗坡县| 岚皋县| 含山县| 峡江县| 连平县| 金堂县|