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

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

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

概念:

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

使用場(chǎng)景:

停止信號(hào)監(jiān)聽(tīng)

定時(shí)任務(wù)

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

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

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

通過(guò)var聲明或者make函數(shù)創(chuàng)建的channel變量是一個(gè)存儲(chǔ)在函數(shù)棧幀上的指針,占用8個(gè)字節(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分為無(wú)緩沖和有緩沖兩種。
 // 對(duì)于有緩沖的channel存儲(chǔ)數(shù)據(jù),使用了 ring buffer(環(huán)形緩沖區(qū)) 來(lái)緩存寫入的數(shù)據(jù),本質(zhì)是循環(huán)數(shù)組
 // 為啥是循環(huán)數(shù)組?普通數(shù)組不行嗎,普通數(shù)組容量固定更適合指定的空間,彈出元素時(shí),普通數(shù)組需要全部都前移
 // 當(dāng)下標(biāo)超過(guò)數(shù)組容量后會(huì)回到第一個(gè)位置,所以需要有兩個(gè)字段記錄當(dāng)前讀和寫的下標(biāo)位置
 buf      unsafe.Pointer // 指向底層循環(huán)數(shù)組的指針(環(huán)形緩沖區(qū))
 qcount   uint           // 循環(huán)數(shù)組中的元素?cái)?shù)量
 dataqsiz uint           // 循環(huán)數(shù)組的長(zhǎng)度
 elemsize uint16                 // 元素的大小
 sendx    uint           // 下一次寫下標(biāo)的位置
 recvx    uint           // 下一次讀下標(biāo)的位置
 // 嘗試讀取channel或向channel寫入數(shù)據(jù)而被阻塞的goroutine
 recvq    waitq  // 讀等待隊(duì)列
 sendq    waitq  // 寫等待隊(duì)列
 lock mutex //互斥鎖,保證讀寫channel時(shí)不存在并發(fā)競(jìng)爭(zhēng)問(wèn)題
}

等待隊(duì)列:

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

每個(gè)節(jié)點(diǎn)是一個(gè)sudog結(jié)構(gòu)體變量,記錄哪個(gè)協(xié)程在等待,等待的是哪個(gè)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) 來(lái)創(chuàng)建 channel,make 語(yǔ)法會(huì)在編譯時(shí),轉(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)建時(shí)會(huì)做一些檢查:

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

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

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

發(fā)送

發(fā)送操作,編譯時(shí)轉(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í)大概分為兩大塊:檢查和數(shù)據(jù)發(fā)送,數(shù)據(jù)發(fā)送流程如下:

如果 channel 的讀等待隊(duì)列存在接收者goroutine

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

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

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

接收

發(fā)送操作,編譯時(shí)轉(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)閉時(shí),for循環(huán)會(huì)自動(dòng)退出,無(wú)需主動(dòng)監(jiān)測(cè)channel是否關(guān)閉,可以防止讀取已經(jīng)關(guān)閉的channel,造成讀到數(shù)據(jù)為通道所存儲(chǔ)的數(shù)據(jù)類型的零值
for i := range ch {
    fmt.Println(i)
}

非阻塞式:

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

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

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

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

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

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

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

關(guān)閉

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

close(ch)
func closechan(c *hchan) 

案例分析:

package main
import (
    "fmt"
    "time"
    "unsafe"
)
func main() {
  // ch是長(zhǎng)度為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ù)時(shí),首先會(huì)對(duì)buf加鎖,然后將task存儲(chǔ)的數(shù)據(jù)copy到buf中,然后sendx++,然后釋放對(duì)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的時(shí)候,會(huì)首先對(duì)buf加鎖,然后將buf中的數(shù)據(jù)copy到task變量對(duì)應(yīng)的內(nèi)存里,然后recvx++,并釋放鎖
func receiveTask(ch chan string) {
    for {
        task := <-ch                  //接收任務(wù)
        fmt.Println("received", task) //處理任務(wù)
    }
}

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

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

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

相關(guān)文章

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

    在Go中使用jwt的教程詳解

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

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

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

    一文帶你了解Go語(yǔ)言中的單元測(cè)試

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

    go語(yǔ)言使用io和bufio包進(jìn)行流操作示例詳解

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

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

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

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

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

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

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

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

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

    golang之資源釋放/異常錯(cuò)誤處理解析

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

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

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

最新評(píng)論

泉州市| 铁岭市| 天门市| 广元市| 革吉县| 南康市| 泸西县| 汉沽区| 巴南区| 喀喇沁旗| 随州市| 松江区| 房产| 延吉市| 汾阳市| 隆回县| 甘泉县| 光泽县| 古交市| 广元市| 彩票| 富阳市| 上饶市| 拉萨市| 紫阳县| 永和县| 灵石县| 靖江市| 兴仁县| 马尔康县| 朝阳县| 永登县| 三穗县| 彭州市| 和静县| 正镶白旗| 民丰县| 六枝特区| 文山县| 镇巴县| 开江县|