golang channel詳解

channel是什么杨何?

  golang語言中,channel是一個協(xié)程安全的FIFO的隊列,讀取和寫入操作都是原子操作

使用場景

  用來做多協(xié)程之間的通信凛澎,java中的線程之間的通信是通過共享內(nèi)存實現(xiàn)的牍戚,A線程獲取內(nèi)存區(qū)域仿耽,并且“鎖”住內(nèi)存這塊區(qū)域,然后執(zhí)行臨界區(qū)代碼错负,這一時刻無法獲取鎖的其他線程阻塞,直到A線程釋放鎖(實際上就是釋放剛才鎖住的內(nèi)存區(qū)域)勇边,其他線程繼續(xù)競爭共享內(nèi)存犹撒,獲取鎖的執(zhí)行臨界區(qū)代碼。這個過程本質(zhì)上是通過共享內(nèi)存的方式實現(xiàn)多線程通信粥诫。而golang提出了新的通信方式:用通信來共享內(nèi)存姐呐,而不要用共享內(nèi)存來通信

使用方式
##無緩沖區(qū)的channel
創(chuàng)建 var NoRoutChannel chan 【類型】= make(chan 【類型】)
使用場景
只是作為信號的channel,告訴另一個協(xié)程谍珊,這件事我做完了搀别,而不需要給另外的協(xié)程序發(fā)送做完后的通知內(nèi)容
##有緩沖區(qū)的channel
創(chuàng)建 var NoRoutChannel chan 【類型】= make(chan 【類型】,【緩沖大小】)
使用場景
告訴另一個協(xié)程,這件事我做完了执赡,并且發(fā)送和另一個協(xié)程序傳遞的變量
PS:注意channel讀取和寫入操作必須在兩個不同的協(xié)程中進行镰踏,否則panic
channel狀態(tài)
channel分為nil、open沙合、closed

image.png

對于nil的channel無論讀寫都panic奠伪,對于closed狀態(tài)的channel,向里邊push的操作會報panic
demo: 使用channel實現(xiàn)一個生產(chǎn)者消費者模式

var wg sync.WaitGroup = sync.WaitGroup{}
type Product struct {
Queue chan string
}
func (product *Product) product(apple string){
fmt.Println("product: "+apple)
product.Queue <- apple
}
type Consumer struct {
Queue chan string
}
func (consumer *Consumer) Consume(){
con := <- consumer.Queue
fmt.Println("consume:"+con)
defer wg.Done()
}
func main() {
testChan := make(chan string,1)
p := Product{
Queue: testChan,
}
c := Consumer{
Queue: testChan,
}
go c.Consume()
go p.product("3333")
wg.Add(1)
wg.Wait()
fmt.Println("end job")
}

channel內(nèi)部原理

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

type hchan struct {
qcount   uint           // 所有在隊列中數(shù)據(jù)數(shù)量
dataqsiz uint           // 環(huán)形隊列大小首懈,可以存放的元素個數(shù)
buf      unsafe.Pointer // 只想環(huán)形隊列的指針
elemsize uint16
closed   uint32
elemtype *_type // element type
sendx    uint   // 生產(chǎn)下標(biāo)
recvx    uint   // 消費下標(biāo)
recvq    waitq  // 消費者隊列
sendq    waitq  // 生產(chǎn)者隊列

// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}

channel緩沖區(qū)實現(xiàn)--環(huán)形隊列

 chan內(nèi)部實現(xiàn)了一個環(huán)形隊列作為其緩沖區(qū)绊率,隊列的長度是創(chuàng)建chan時指定的。

下圖展示了一個可緩存6個元素的channel示意圖:

image
  • dataqsiz指示了隊列長度為6究履,即可緩存6個元素滤否;
  • buf指向隊列的內(nèi)存,隊列中還剩余兩個元素最仑;
  • qcount表示隊列中還有兩個元素藐俺;
  • sendx指示后續(xù)寫入的數(shù)據(jù)存儲的位置炊甲,取值[0, 6);
  • recvx指示從該位置讀取數(shù)據(jù), 取值[0, 6)欲芹;

等待隊列

從channel讀數(shù)據(jù)卿啡,如果channel緩沖區(qū)為空或者沒有緩沖區(qū),當(dāng)前goroutine會被阻塞菱父。
向channel寫數(shù)據(jù)颈娜,如果channel緩沖區(qū)已滿或者沒有緩沖區(qū),當(dāng)前goroutine會被阻塞滞伟。

被阻塞的goroutine將會掛在channel的等待隊列中:

  • 因讀阻塞的goroutine會被向channel寫入數(shù)據(jù)的goroutine喚醒揭鳞;
  • 因?qū)懽枞膅oroutine會被從channel讀數(shù)據(jù)的goroutine喚醒;

下圖展示了一個沒有緩沖區(qū)的channel梆奈,有幾個goroutine阻塞等待讀數(shù)據(jù):

image

注意野崇,一般情況下recvq和sendq至少有一個為空。只有一個例外亩钟,那就是同一個goroutine使用select語句向channel一邊寫數(shù)據(jù)乓梨,一邊讀數(shù)據(jù)。

make(chan string,2)分析

代碼:

func makechan(t *chantype, size int) *hchan {
var c hchan
c = new(hchan)
c.buf = malloc(元素類型大小
size)
c.elemsize = 元素類型大小
c.elemtype = 元素類型
c.dataqsiz = size
return c
}

向channel寫數(shù)據(jù)

向一個channel中寫數(shù)據(jù)簡單過程如下:

  1. 如果等待接收隊列recvq不為空清酥,說明緩沖區(qū)中沒有數(shù)據(jù)或者沒有緩沖區(qū)扶镀,此時直接從recvq取出G,并把數(shù)據(jù)寫入,最后把該G喚醒焰轻,結(jié)束發(fā)送過程臭觉;
  2. 如果緩沖區(qū)中有空余位置,將數(shù)據(jù)寫入緩沖區(qū)辱志,結(jié)束發(fā)送過程蝠筑;
  3. 如果緩沖區(qū)中沒有空余位置,將待發(fā)送數(shù)據(jù)寫入G揩懒,將當(dāng)前G加入sendq什乙,進入睡眠,等待被讀goroutine喚醒已球;

簡單流程圖如下:


image

從channel中讀取數(shù)據(jù)

從一個channel讀數(shù)據(jù)簡單過程如下:

  1. 如果等待發(fā)送隊列sendq不為空臣镣,且沒有緩沖區(qū),直接從sendq中取出G智亮,把G中數(shù)據(jù)讀出忆某,最后把G喚醒,結(jié)束讀取過程阔蛉;
  2. 如果等待發(fā)送隊列sendq不為空弃舒,此時說明緩沖區(qū)已滿,從緩沖區(qū)中首部讀出數(shù)據(jù)馍忽,把G中數(shù)據(jù)寫入緩沖區(qū)尾部棒坏,把G喚醒,結(jié)束讀取過程遭笋;
  3. 如果緩沖區(qū)中有數(shù)據(jù)坝冕,則從緩沖區(qū)取出數(shù)據(jù),結(jié)束讀取過程瓦呼;
  4. 將當(dāng)前goroutine加入recvq喂窟,進入睡眠,等待被寫goroutine喚醒央串;

簡單流程圖如下:


image

關(guān)閉channel

關(guān)閉channel時會把recvq中的G全部喚醒磨澡,本該寫入G的數(shù)據(jù)位置為nil。把sendq中的G全部喚醒质和,但這些G會panic稳摄。

除此之外,panic出現(xiàn)的常見場景還有:

關(guān)閉值為nil的channel
關(guān)閉已經(jīng)被關(guān)閉的channel
向已經(jīng)關(guān)閉的channel寫數(shù)據(jù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饲宿,一起剝皮案震驚了整個濱河市厦酬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘫想,老刑警劉巖仗阅,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異国夜,居然都是意外死亡减噪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門车吹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筹裕,“玉大人,你說我怎么就攤上這事礼搁∪牡猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵馒吴,是天一觀的道長扎运。 經(jīng)常有香客問我,道長饮戳,這世上最難降的妖魔是什么豪治? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮扯罐,結(jié)果婚禮上负拟,老公的妹妹穿的比我還像新娘。我一直安慰自己歹河,他們只是感情好掩浙,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布花吟。 她就那樣靜靜地躺著,像睡著了一般厨姚。 火紅的嫁衣襯著肌膚如雪衅澈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天谬墙,我揣著相機與錄音今布,去河邊找鬼。 笑死拭抬,一個胖子當(dāng)著我的面吹牛部默,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播造虎,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼傅蹂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了算凿?” 一聲冷哼從身側(cè)響起贬派,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澎媒,沒想到半個月后搞乏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡戒努,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年请敦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片储玫。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡侍筛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撒穷,到底是詐尸還是另有隱情匣椰,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布端礼,位于F島的核電站禽笑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛤奥。R本人自食惡果不足惜佳镜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凡桥。 院中可真熱鬧蟀伸,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迟蜜,卻和暖如春谢肾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背小泉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冕杠,地道東北人微姊。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像分预,于是被迫代替她去往敵國和親兢交。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345