亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

golang?channel多協(xié)程通信常用方法底層原理全面解析

 更新時間:2023年09月27日 10:01:24   作者:lincoln_hlf1  
channel?是?goroutine?與?goroutine?之間通信的重要橋梁,借助?channel,我們能很輕易的寫出一個多協(xié)程通信程序,今天,我們就來看看這個?channel?的常用用法以及底層原理

一、channel 的概念

channel 是一個通道,用于端到端的數(shù)據(jù)傳輸,這有點像我們平常使用的消息隊列,只不過 channel 的發(fā)送方和接受方是 goroutine 對象,屬于內存級別的通信。

這里涉及到了 goroutine 概念,goroutine 是輕量級的協(xié)程,有屬于自己的??臻g。 我們可以把它理解為線程,只不過 goroutine 的性能開銷很小,并且在用戶態(tài)上實現(xiàn)了屬于自己的調度模型。

傳統(tǒng)的線程通信有很多方式,像內存共享、信號量等。其中內存共享實現(xiàn)較為簡單,只需要對變量進行并發(fā)控制,加鎖即可。但這種在后續(xù)業(yè)務逐漸復雜時,將很難維護,耦合性也比較強。

后來提出了 CSP 模型,即在通信雙方抽象出中間層,數(shù)據(jù)的流轉由中間層來控制,通信雙方只負責數(shù)據(jù)的發(fā)送和接收,從而實現(xiàn)了數(shù)據(jù)的共享,這就是所謂的通過通信來共享內存。 channel 就是按這個模型來實現(xiàn)的。

channel 在多并發(fā)操作里是屬于協(xié)程安全的,并且遵循了 FIFO 特性。即先執(zhí)行讀取的 goroutine 會先獲取到數(shù)據(jù),先發(fā)送數(shù)據(jù)的 goroutine 會先輸入數(shù)據(jù)。

另外,channel 的使用將會引起 Go runtime 的調度調用,會有阻塞和喚起 goroutine 的情況產生。

二、channel 的使用

在深入了解 channel 的底層之前,我們先來看看 channel 的常用用法。

channel 的創(chuàng)建

    ch := make(chan int)

上面是創(chuàng)建了無緩沖的 channel,一旦有 goroutine 往 channel 發(fā)送數(shù)據(jù),那么當前的 goroutine 會被阻塞住,直到有其他的 goroutine 消費了 channel 里的數(shù)據(jù),才能繼續(xù)運行。

還有另外一種是有緩沖的 channel,它的創(chuàng)建是這樣的:

ch := make(chan int, 2)

第二個參數(shù)表示 channel 可緩沖數(shù)據(jù)的容量。只要當前 channel 里的元素總數(shù)不大于這個可緩沖容量,則當前的 goroutine 就不會被阻塞住。

需要注意的是,上面 make 后返回的是一個指向 hchan 結構的指針變量,等會將會聊聊 hchan 的底層結構。

另外,我們也可以聲明一個 nil 的 channel,只是創(chuàng)建這樣的 channel 沒有意義,讀、寫 channel 都將會被阻塞住。一般 nil channel 用在 select 上,讓 select 不再從這個 channel 里讀取數(shù)據(jù),如下用法:

    ch1 := make(chan int)
    ch2 := make(chan int)
    go func() {
        if !ok { // 某些原因,設置 ch1 為 nil
            ch1 = nil
        }
    }()
    for {
        select {
        case <-ch1: // 當 ch1 被設置為 nil 后,將不會到達此分支了。
            doSomething1()
        case <-ch2:
            doSomething2()
        }
    }

使用 channel 時我們還可以控制 channel 只讀只寫操作:

    func readChan(ch <-chan int){
        // chan 只允許被讀
    }
    func main(){
        ch := make(chan int)
        readChan(ch)
    }

反之,如果要求只寫操作,則可以這樣:

    func writeChan(ch chan<- int){
        // chan 只允許被寫
    }

channel 的讀寫

往一個 channel 發(fā)送數(shù)據(jù),可以這樣

    ch := make(chan int)
    ch <- 1

對應的操作:

    data <- ch

當我們不再使用 channel 的時候,可以對其進行關閉:

    close(ch)

當 channel 被關閉后,如果繼續(xù)往里面寫數(shù)據(jù),則程序會直接 panic 退出。

不過讀取關閉后的 channel,不會產生 pannic,還是可以讀到數(shù)據(jù)。

如果關閉后的 channel 沒有數(shù)據(jù)可讀取時,將得到零值,即對應類型的默認值。

為了能知道當前 channel 是否被關閉,可以使用下面的寫法來判斷。

    if v, ok := <-ch; !ok {
        fmt.Println("channel 已關閉,讀取不到數(shù)據(jù)")
    }

還可以使用下面的寫法不斷的獲取 channel 里的數(shù)據(jù):

    for data := range ch {
        // get data dosomething
    }

這種用法會在讀取完 channel 里的數(shù)據(jù)后就結束 for 循環(huán),執(zhí)行后面的代碼。

channel 和 select

在寫程序時,有時并不單單只會和一個 goroutine 通信,當我們要進行多 goroutine 通信時,則會使用 select 寫法來管理多個 channel 的通信數(shù)據(jù):

    ch1 := make(chan struct{})
    ch2 := make(chan struct{})
    // ch1, ch2 發(fā)送數(shù)據(jù)
    go sendCh1(ch1)
    go sendCh1(ch2)
    // channel 數(shù)據(jù)接受處理
    for {
        select {
        case <-ch1:
            doSomething1()
        case <-ch2:
            doSomething2()
        }
    }

channel 的 deadlock

前面提到過,往 channel 里讀寫數(shù)據(jù)時是有可能被阻塞住的,一旦被阻塞,則需要其他的 goroutine 執(zhí)行對應的讀寫操作,才能解除阻塞狀態(tài)。

然而,阻塞后一直沒能發(fā)生調度行為,沒有可用的 goroutine 可執(zhí)行,則會一直卡在這個地方,程序就失去執(zhí)行意義了。此時 Go 就會報 deadlock 錯誤,如下代碼:

    func main() {
        ch := make(chan int)
        <-ch
        // 執(zhí)行后將 panic:
        // fatal error: all goroutines are asleep - deadlock!
    }

因此,在使用 channel 時要注意 goroutine 的一發(fā)一取,避免 goroutine 永久阻塞!

三、channel 的底層原理

前面提及過 channel 創(chuàng)建后返回了 hchan 結構體,現(xiàn)在我們來研究下這個結構體,它的主要字段如下:

type hchan struct {
    qcount   uint   // channel 里的元素計數(shù)
    dataqsiz uint   // 可以緩沖的數(shù)量,如 ch := make(chan int, 10)。 此處的 10 即 dataqsiz
    elemsize uint16 // 要發(fā)送或接收的數(shù)據(jù)類型大小
    buf      unsafe.Pointer // 當 channel 設置了緩沖數(shù)量時,該 buf 指向一個存儲緩沖數(shù)據(jù)的區(qū)域,該區(qū)域是一個循環(huán)隊列的數(shù)據(jù)結構
    closed   uint32 // 關閉狀態(tài)
    sendx    uint  // 當 channel 設置了緩沖數(shù)量時,數(shù)據(jù)區(qū)域即循環(huán)隊列此時已發(fā)送數(shù)據(jù)的索引位置
    recvx    uint  // 當 channel 設置了緩沖數(shù)量時,數(shù)據(jù)區(qū)域即循環(huán)隊列此時已接收數(shù)據(jù)的索引位置
    recvq    waitq // 想讀取數(shù)據(jù)但又被阻塞住的 goroutine 隊列
    sendq    waitq // 想發(fā)送數(shù)據(jù)但又被阻塞住的 goroutine 隊列
    lock mutex
    ...
}

channel 在進行讀寫數(shù)據(jù)時,會根據(jù)無緩沖、有緩沖設置進行對應的阻塞喚起動作,它們之間還是有區(qū)別的。下面我們來捋一下這些不同之處。

無緩沖 channel

由于對 channel 的讀寫先后順序不同,處理也會有所不同,所以,還得再進一步區(qū)分:

channel 先寫再讀

在這里,我們暫時認為有 2 個 goroutine 在使用 channel 通信,按先寫再讀的順序,則具體流程如下:

可以看到,由于 channel 是無緩沖的,所以 G1 暫時被掛在 sendq 隊列里,然后 G1 調用了 gopark 休眠了起來。

接著,又有 goroutine 來 channel 讀取數(shù)據(jù)了:

此時 G2 發(fā)現(xiàn) sendq 等待隊列里有 goroutine 存在,于是直接從 G1 copy 數(shù)據(jù)過來,并且會對 G1 設置 goready 函數(shù),這樣下次調度發(fā)生時, G1 就可以繼續(xù)運行,并且會從等待隊列里移除掉。

channel 先讀再寫

先讀再寫的流程跟上面一樣。

G1 暫時被掛在了 recvq 隊列,然后休眠起來。

G2 在寫數(shù)據(jù)時,發(fā)現(xiàn) recvq 隊列有 goroutine 存在,于是直接將數(shù)據(jù)發(fā)送給 G1。同時設置 G1 goready 函數(shù),等待下次調度運行。

有緩沖 channel

在分析完了無緩沖 channel 的讀寫后,我們繼續(xù)看看有緩沖 channel 的讀寫。同樣的,我們分為 2 種情況:

channel 先寫再讀

這一次會優(yōu)先判斷緩沖數(shù)據(jù)區(qū)域是否已滿,如果未滿,則將數(shù)據(jù)保存在緩沖數(shù)據(jù)區(qū)域,即環(huán)形隊列里。如果已滿,則和之前的流程是一樣的。

當 G2 要讀取數(shù)據(jù)時,會優(yōu)先從緩沖數(shù)據(jù)區(qū)域去讀取,并且在讀取完后,會檢查 sendq 隊列,如果 goroutine 有等待隊列,則會將它上面的 data 補充到緩沖數(shù)據(jù)區(qū)域,并且也對其設置 goready 函數(shù)。

channel 先讀再寫

此種情況和無緩沖的先讀再寫是一樣流程,此處不再重復說明。

總結

有緩沖 channel 和無緩沖 channel 的讀寫基本相差不大,只是多了緩沖數(shù)據(jù)區(qū)域的判斷而已。

channel 在使用的時候大多時候得和 select 配合使用,盡管只需要簡單的用 <- ch 和 ch <- 來讀寫數(shù)據(jù),但它的底層還是很有講究的,特別是涉及到調度的休眠喚起。

這也能看出 Go 的精妙之處:復雜底層,優(yōu)雅運用。

以上就是golang channel多協(xié)程通信常用方法底層原理全面解析的詳細內容,更多關于golang channel多協(xié)程通信的資料請關注腳本之家其它相關文章!

相關文章

  • 一文詳解Golang的中間件設計模式

    一文詳解Golang的中間件設計模式

    最近在看一些rpc框架的使用原理和源碼的時候,對中間件的實現(xiàn)非常感興趣,所以這篇文章就來和大家聊聊Golang的中間件設計模式,希望對大家有所幫助
    2023-03-03
  • PHP和GO對接ChatGPT實現(xiàn)聊天機器人效果實例

    PHP和GO對接ChatGPT實現(xiàn)聊天機器人效果實例

    這篇文章主要為大家介紹了PHP和GO對接ChatGPT實現(xiàn)聊天機器人效果實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • Golang中使用JSON的一些小技巧分享

    Golang中使用JSON的一些小技巧分享

    這篇文章主要分享了Golang中使用JSON的一些小技巧,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-06-06
  • 詳解Go語言如何高效解壓ZIP文件

    詳解Go語言如何高效解壓ZIP文件

    在日常開發(fā)中,我們經常需要處理 ZIP 文件,本文主要為大家介紹一個使用 Go 語言編寫的高效 ZIP 文件解壓工具,希望對大家有一定的幫助
    2025-03-03
  • golang實現(xiàn)并發(fā)數(shù)控制的方法

    golang實現(xiàn)并發(fā)數(shù)控制的方法

    下面小編就為大家分享一篇golang實現(xiàn)并發(fā)數(shù)控制的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Go語言展現(xiàn)快速排序算法全過程的思路及代碼示例

    Go語言展現(xiàn)快速排序算法全過程的思路及代碼示例

    這篇文章主要介紹了Go語言展現(xiàn)快速排序算法全過程的思路及代碼示例,文章最后作者還提到了對Quick Sort算法優(yōu)化的一些想法,需要的朋友可以參考下
    2016-04-04
  • golangci-lint安裝與Goland集成問題

    golangci-lint安裝與Goland集成問題

    這篇文章主要介紹了golangci-lint安裝與Goland集成,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • 一文詳解如何使用Go語言生成二維碼

    一文詳解如何使用Go語言生成二維碼

    使用Go語言編程時,生成任意內容的二維碼是非常方便的,下面這篇文章主要給大家介紹了關于如何使用Go語言生成二維碼的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-01-01
  • 8個Elasticsearch高頻面試題和答案整理

    8個Elasticsearch高頻面試題和答案整理

    這篇文章為大家精選了8道Elasticsearch高頻面試題和答案,并且給出了這些知識點的應用場景、也給出了解決這些問題的思路,希望對大家有所幫助
    2023-06-06
  • go語言實現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例

    go語言實現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例

    這篇文章主要為大家介紹了go語言實現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-04-04

最新評論