go語言channel go語言channel 的用法

Golang 語言深入理解:channel

本文是對(duì) Gopher 2017 中一個(gè)非常好的 Talk?: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的學(xué)習(xí)筆記,希望能夠通過對(duì) channel 的關(guān)鍵特性的理解,進(jìn)一步掌握其用法細(xì)節(jié)以及 Golang 語言設(shè)計(jì)哲學(xué)的管窺蠡測(cè)。

成都地區(qū)優(yōu)秀IDC服務(wù)器托管提供商(成都創(chuàng)新互聯(lián)公司).為客戶提供專業(yè)的多線服務(wù)器托管,四川各地服務(wù)器托管,多線服務(wù)器托管、多線服務(wù)器托管.托管咨詢專線:028-86922220

channel 是可以讓一個(gè) goroutine 發(fā)送特定值到另一個(gè) gouroutine 的通信機(jī)制。

原生的 channel 是沒有緩存的(unbuffered channel),可以用于 goroutine 之間實(shí)現(xiàn)同步。

關(guān)閉后不能再寫入,可以讀取直到 channel 中再?zèng)]有數(shù)據(jù),并返回元素類型的零值。

gopl/ch3/netcat3

首先從 channel 是怎么被創(chuàng)建的開始:

在 heap 上分配一個(gè) hchan 類型的對(duì)象,并將其初始化,然后返回一個(gè)指向這個(gè) hchan 對(duì)象的指針。

理解了 channel 的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),現(xiàn)在轉(zhuǎn)到 channel 的兩個(gè)最基本方法: sends 和 receivces ,看一下以上的特性是如何體現(xiàn)在 sends 和 receives 中的:

假設(shè)發(fā)送方先啟動(dòng),執(zhí)行 ch - task0 :

如此為 channel 帶來了 goroutine-safe 的特性。

在這樣的模型里, sender goroutine - channel - receiver goroutine 之間, hchan 是唯一的共享內(nèi)存,而這個(gè)唯一的共享內(nèi)存又通過 mutex 來確保 goroutine-safe ,所有在隊(duì)列中的內(nèi)容都只是副本。

這便是著名的 golang 并發(fā)原則的體現(xiàn):

發(fā)送方 goroutine 會(huì)阻塞,暫停,并在收到 receive 后才恢復(fù)。

goroutine 是一種 用戶態(tài)線程 , 由 Go runtime 創(chuàng)建并管理,而不是操作系統(tǒng),比起操作系統(tǒng)線程來說,goroutine更加輕量。

Go runtime scheduler 負(fù)責(zé)將 goroutine 調(diào)度到操作系統(tǒng)線程上。

runtime scheduler 怎么將 goroutine 調(diào)度到操作系統(tǒng)線程上?

當(dāng)阻塞發(fā)生時(shí),一次 goroutine 上下文切換的全過程:

然而,被阻塞的 goroutine 怎么恢復(fù)過來?

阻塞發(fā)生時(shí),調(diào)用 runtime sheduler 執(zhí)行 gopark 之前,G1 會(huì)創(chuàng)建一個(gè) sudog ,并將它存放在 hchan 的 sendq 中。 sudog 中便記錄了即將被阻塞的 goroutine G1 ,以及它要發(fā)送的數(shù)據(jù)元素 task4 等等。

接收方 將通過這個(gè) sudog 來恢復(fù) G1

接收方 G2 接收數(shù)據(jù), 并發(fā)出一個(gè) receivce ,將 G1 置為 runnable :

同樣的, 接收方 G2 會(huì)被阻塞,G2 會(huì)創(chuàng)建 sudoq ,存放在 recvq ,基本過程和發(fā)送方阻塞一樣。

不同的是,發(fā)送方 G1如何恢復(fù)接收方 G2,這是一個(gè)非常神奇的實(shí)現(xiàn)。

理論上可以將 task 入隊(duì),然后恢復(fù) G2, 但恢復(fù) G2后,G2會(huì)做什么呢?

G2會(huì)將隊(duì)列中的 task 復(fù)制出來,放到自己的 memory 中,基于這個(gè)思路,G1在這個(gè)時(shí)候,直接將 task 寫到 G2的 stack memory 中!

這是違反常規(guī)的操作,理論上 goroutine 之間的 stack 是相互獨(dú)立的,只有在運(yùn)行時(shí)可以執(zhí)行這樣的操作。

這么做純粹是出于性能優(yōu)化的考慮,原來的步驟是:

優(yōu)化后,相當(dāng)于減少了 G2 獲取鎖并且執(zhí)行 memcopy 的性能消耗。

channel 設(shè)計(jì)背后的思想可以理解為 simplicity 和 performance 之間權(quán)衡抉擇,具體如下:

queue with a lock prefered to lock-free implementation:

比起完全 lock-free 的實(shí)現(xiàn),使用鎖的隊(duì)列實(shí)現(xiàn)更簡(jiǎn)單,容易實(shí)現(xiàn)

每天一個(gè)知識(shí)點(diǎn):Go?語言當(dāng)中?Channel(通道)有什么特點(diǎn),需要注意什么?

使用簡(jiǎn)單的 make 調(diào)用創(chuàng)建的通道叫做無緩沖通道,但 make 還可以接受第二個(gè)可選參數(shù),一個(gè)表示通道容量的整數(shù)。如果容量是 0,make 創(chuàng)建一個(gè)無緩沖通道。

無緩沖通道上的發(fā)送操作將被阻塞,直到另一個(gè) goroutine 在對(duì)應(yīng)的通道上執(zhí)行接受操作,這時(shí)值傳送完成,兩個(gè) goroutine 都可以繼續(xù)執(zhí)行。相反,如果接受操作先執(zhí)行,接收方 goroutine 將阻塞,直到另一個(gè) goroutine 在同一個(gè)通道上發(fā)送一個(gè)值。使用無緩沖通道進(jìn)行的通信導(dǎo)致發(fā)送和接受操作 goroutine 同步化。因此,無緩沖通道也稱為同步通道。當(dāng)一個(gè)值在無緩沖通道上傳遞時(shí),接受值后發(fā)送方 goroutine 才能被喚醒。

緩沖通道上的發(fā)送操作在隊(duì)列的尾部插入一個(gè)元素,接收操作從隊(duì)列的頭部移除一個(gè)元素。如果通道滿了,發(fā)送操作會(huì)阻塞所在的 goroutine 直到另一個(gè) goroutine 對(duì)它進(jìn)行接收操作來留出可用的空間。反過來,如果通道是空的,執(zhí)行接收操作的 goroutine 阻塞,直到另一個(gè) goroutine 在通道上發(fā)送數(shù)據(jù)。

如果給一個(gè) nil 的 channel 發(fā)送數(shù)據(jù),會(huì)造成永遠(yuǎn)阻塞。

如果從一個(gè) nil 的 channel 中接收數(shù)據(jù),也會(huì)造成永久阻塞。 給一個(gè)已經(jīng)關(guān)閉的 channel 發(fā)送數(shù)據(jù), 會(huì)引起 panic。

從一個(gè)已經(jīng)關(guān)閉的 channel 接收數(shù)據(jù), 如果緩沖區(qū)中為空,則返回一個(gè) 零 值。

go語言無緩沖的channel

無緩沖的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道。

這種類型的通道要求發(fā)送goroutine和接收goroutine同時(shí)準(zhǔn)備好,才能完成發(fā)送和接收操作。否則,通道會(huì)導(dǎo)致先執(zhí)行發(fā)送或接收操作的 goroutine 阻塞等待。

這種對(duì)通道進(jìn)行發(fā)送和接收的交互行為本身就是同步的。其中任意一個(gè)操作都無法離開另一個(gè)操作單獨(dú)存在。

阻塞:由于某種原因數(shù)據(jù)沒有到達(dá),當(dāng)前協(xié)程(線程)持續(xù)處于等待狀態(tài),直到條件滿足,才接觸阻塞。

同步:在兩個(gè)或多個(gè)協(xié)程(線程)間,保持?jǐn)?shù)據(jù)內(nèi)容一致性的機(jī)制。

下圖展示兩個(gè) goroutine 如何利用無緩沖的通道來共享一個(gè)值:

在第 1 步,兩個(gè) goroutine 都到達(dá)通道,但哪個(gè)都沒有開始執(zhí)行發(fā)送或者接收。

在第 2 步,左側(cè)的 goroutine 將它的手伸進(jìn)了通道,這模擬了向通道發(fā)送數(shù)據(jù)的行為。這時(shí),這個(gè) goroutine 會(huì)在通道中被鎖住,直到交換完成。

在第 3 步,右側(cè)的 goroutine 將它的手放入通道,這模擬了從通道里接收數(shù)據(jù)。這個(gè) goroutine 一樣也會(huì)在通道中被鎖住,直到交換完成。

在第 4 步和第 5 步,進(jìn)行交換,并最終,在第 6 步,兩個(gè) goroutine 都將它們的手從通道里拿出來,這模擬了被鎖住的 goroutine 得到釋放。兩個(gè) goroutine 現(xiàn)在都可以去做別的事情了。

如果沒有指定緩沖區(qū)容量,那么該通道就是同步的,因此會(huì)阻塞到發(fā)送者準(zhǔn)備好發(fā)送和接收者準(zhǔn)備好接收。

無緩沖channel: —— 同步通信

Go 語言 channel 的阻塞問題

Hello,大家好,又見面了!上一遍我們將 channel 相關(guān)基礎(chǔ)以及使用場(chǎng)景。這一篇,還需要再次進(jìn)階理解channel 阻塞問題。以下創(chuàng)建一個(gè)chan類型為int,cap 為3。

channel 內(nèi)部其實(shí)是一個(gè)環(huán)形buf數(shù)據(jù)結(jié)構(gòu) ,是一種滑動(dòng)窗口機(jī)制,當(dāng)make完后,就分配在 Heap 上。

上面,向 chan 發(fā)送一條“hello”數(shù)據(jù):

如果 G1 發(fā)送數(shù)據(jù)超過指定cap時(shí),會(huì)出現(xiàn)什么情況?

看下面實(shí)例:

以上會(huì)出現(xiàn)什么,chan 緩沖區(qū)允許大小為1,如果再往chan仍數(shù)據(jù),滿了就會(huì)被阻塞,那么是如何實(shí)現(xiàn)阻塞的呢?當(dāng) chan 滿時(shí),會(huì)進(jìn)入 gopark,此時(shí) G1 進(jìn)入一個(gè) waiting 狀態(tài),然后會(huì)創(chuàng)建一個(gè) sudog 對(duì)象,其實(shí)就sendq隊(duì)列,把 200放進(jìn)去。等 buf 不滿的時(shí)候,再喚醒放入buf里面。

通過如下源碼,你會(huì)更加清晰:

上面,從 chan 獲取數(shù)據(jù):

Go 語言核心思想:“Do not communicate by sharing memory; instead, share memory by communicating.” 你可以看看這本書名叫:Effective Go

如果接收者,接收一個(gè)空對(duì)象,也會(huì)發(fā)生什么情況?

代碼示例 :

也會(huì)報(bào)錯(cuò)如下:

上面,從 chan 取出數(shù)據(jù),可是沒有數(shù)據(jù)了。此時(shí),它會(huì)把 接收者 G2 阻塞掉,也是和G1發(fā)送者一樣,也會(huì)執(zhí)行 gopark 將狀態(tài)改為 waiting,不一樣的點(diǎn)就是。

正常情況下,接收者G2作為取出數(shù)據(jù)是去 buf 讀取數(shù)據(jù)的,但現(xiàn)在,buf 為空了,此時(shí),接收者G2會(huì)將sudog導(dǎo)出來,因?yàn)楝F(xiàn)在G2已經(jīng)被阻塞了嘛,會(huì)把G2給G,然后將 t := -ch 中變量 t 是在棧上的地址,放進(jìn)去 elem ,也就是說,只存它的地址指針在sudog里面。

最后, ch - 200 當(dāng)G1往 chan 添加200這個(gè)數(shù)據(jù),正常情況是將數(shù)據(jù)添加到buf里面,然后喚醒 G2 是吧,而現(xiàn)在是將 G1 的添加200數(shù)據(jù)直接干到剛才G2阻塞的t這里變量里面。

你會(huì)認(rèn)為,這樣真的可以嗎?想一想,G2 本來就是已經(jīng)阻塞了,然后我們直接這么干肯定沒有什么毛病,而且效率提高了,不需要再次放入buf再取出,這個(gè)過程也是需要時(shí)間。不然,不得往chan添加數(shù)據(jù)需要加鎖、拷貝、解鎖一序列操作,那肯定就慢了,我想Go語言是為了高效及內(nèi)存使用率的考慮這樣設(shè)計(jì)的。(注意,一般都是在runtime里面完成,不然會(huì)出現(xiàn)象安全問題。)

總結(jié) :

chan 類型的特點(diǎn):chan 如果為空,receiver 接收數(shù)據(jù)的時(shí)候就會(huì)阻塞等待,直到 chan 被關(guān)閉或者有新的數(shù)據(jù)到來。有這種個(gè)機(jī)制,就可以實(shí)現(xiàn) wait/notify 的設(shè)計(jì)模式。

相關(guān)面試題:

go語言的channel特性

1、給一個(gè)nil channel發(fā)送數(shù)據(jù),造成永遠(yuǎn)阻塞

2、從一個(gè)nil channel接收數(shù)據(jù),造成永遠(yuǎn)阻塞

3、給一個(gè)已經(jīng)關(guān)閉的channel發(fā)送數(shù)據(jù),引起panic

4、從一個(gè)已經(jīng)關(guān)閉的channel接收數(shù)據(jù),如果緩沖區(qū)中為空,則返回一個(gè)零值

5、無緩沖的channel是同步的,而有緩沖的channel是非同步的

當(dāng)前題目:go語言channel go語言channel 的用法
網(wǎng)站網(wǎng)址:http://muchs.cn/article4/hheooe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、域名注冊(cè)、網(wǎng)站建設(shè)App設(shè)計(jì)、響應(yīng)式網(wǎng)站服務(wù)器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作