Golang channel 之 寫操作 send

上一篇:Golang channel 之 數(shù)據(jù)結(jié)構(gòu)
下一篇:Golang channel 之 讀操作 recv

channel的常規(guī)寫操作

假如有一個(gè)元素類型為int的channel,變量名為ch理肺,那么常規(guī)的寫操作(簡稱send為寫)在代碼中的寫法如下所示:

ch <- 10

其中ch可能是“有緩沖”的,也可能是“無緩沖”的善镰,甚至可能為nil妹萨。

按照上面的寫法,有兩種情況能使寫操作不會(huì)阻塞:
1)通道ch的recvq里已有g(shù)oroutine在等待炫欺;
2)通道ch是“有緩沖”的乎完,且緩沖區(qū)沒有用盡。

第一種情況中品洛,只要ch的recvq里有協(xié)程在排隊(duì)树姨,當(dāng)前協(xié)程就直接把數(shù)據(jù)交給recvq隊(duì)首的那個(gè)協(xié)程就好了,然后兩個(gè)協(xié)程都可以繼續(xù)執(zhí)行桥状,無關(guān)ch有沒有緩沖娃弓;

第二種情況中,ch是有緩沖的岛宦,且緩沖區(qū)沒有用盡,也就是底層數(shù)組沒有存滿耍缴,那么當(dāng)前協(xié)程直接把數(shù)據(jù)追加到緩沖數(shù)組中砾肺,就可以繼續(xù)執(zhí)行。

同樣是上面的寫法防嗡,有三種情況會(huì)使寫操作阻塞:
1)通道ch為nil变汪;
2)通道ch無緩沖且recvq為空;
3)通道ch有緩沖且緩沖區(qū)已用盡蚁趁。

第一種情況中裙盾,參照golang的實(shí)現(xiàn),允許對(duì)nil通道執(zhí)行寫操作他嫡,但是會(huì)使當(dāng)前協(xié)程永久性的阻塞在這個(gè)nil通道上番官,例如如下代碼會(huì)因死鎖拋出異常:

package main

func main() {
        var ch chan int
        ch <- 10
}

第二種情況中,ch為無緩沖通道钢属,recvq中沒有協(xié)程在等待徘熔,所以當(dāng)前協(xié)程需要到通道的sendq中排隊(duì);

第三種情況中淆党,ch有緩沖且已用盡酷师,隱含的信息就是recvq為空讶凉,否則緩沖不會(huì)用盡,所以當(dāng)前協(xié)程只能到sendq中排隊(duì)山孔。

channel的非阻塞寫操作

熟悉并發(fā)編程的同學(xué)應(yīng)該知道懂讯,有些鎖是支持tryLock操作的,也就是我想獲得這把鎖台颠,但是萬一已經(jīng)被別人獲得了褐望,我不阻塞等待,可以去干其他事情蓉媳。

對(duì)于通道的非阻塞寫就是:我想向通道寫數(shù)據(jù)譬挚,但是如果當(dāng)前沒有讀者在排隊(duì)等待,且緩沖區(qū)沒有剩余空間(包含無緩沖)酪呻,我就需要阻塞等待减宣。但是我不想等待,所以立刻返回并告訴我“現(xiàn)在不能寫”就可以了玩荠。

在golang中漆腌,對(duì)于單個(gè)通道的非阻塞寫操作可以用如下代碼實(shí)現(xiàn),注意是一個(gè)select阶冈、一個(gè)case和一個(gè)default闷尿,都是一個(gè),不能多也不能少:

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

如果檢測到寫ch不會(huì)阻塞女坑,那么就會(huì)執(zhí)行case ch <- 10:分支填具,如果會(huì)阻塞,就會(huì)執(zhí)行default:分支匆骗。關(guān)于什么情況下會(huì)阻塞劳景,什么情況下不會(huì)阻塞,參見上面的情況分析碉就。

channel寫操作的實(shí)現(xiàn)

上面簡單的分析了channel的常規(guī)寫操作和非阻塞寫操作盟广,雖然兩者在形式上看起來稍微有些差異,但是主要邏輯都是通過runtime.chansend函數(shù)實(shí)現(xiàn)的瓮钥,下面簡單的進(jìn)行一下解讀:

首先來看一下chansend函數(shù)的原型:

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool

其中:
c是一個(gè)hchan指針筋量,指向要用來send數(shù)據(jù)的channel;
ep是一個(gè)指針碉熄,指向要被寫入通道c的數(shù)據(jù)桨武,數(shù)據(jù)類型要和c的元素類型一致;
block表示如果寫操作不能立即完成锈津,是否想要阻塞等待玻募;
callerpc用以進(jìn)行race相關(guān)檢測,暫時(shí)不需要關(guān)心一姿;
返回值為true表示數(shù)據(jù)寫入完成七咧,false表示目前不可寫但因?yàn)椴幌胱枞╞lock為false)而返回跃惫。

chansend函數(shù)的主要邏輯如下:

如果c等于nil {
    如果不想阻塞 {
        return false
    }
    永久阻塞
}

如果(不想阻塞 且 c未關(guān)閉) 且 ((c無緩沖 且 recvq是空的) 或 (c有緩沖 且 緩沖區(qū)已滿)) {
    return false
}

對(duì)c加鎖

如果c已關(guān)閉 {
    解鎖c
    panic("send on closed channel")
}

如果recvq中有內(nèi)容 {
    取出隊(duì)首協(xié)程,并把數(shù)據(jù)傳遞給它并解鎖c
    return true
}

如果緩沖區(qū)還有空間 {
    把數(shù)據(jù)追加到緩沖區(qū)艾栋,移動(dòng)sendx爆存,遞增qcount
    解鎖c
    return true
}

如果不想阻塞 {
    解鎖c
    return false
}

進(jìn)入sendq排隊(duì)等待同時(shí)解鎖c,條件滿足時(shí)會(huì)完成數(shù)據(jù)傳遞并被喚醒

return true

逐塊對(duì)應(yīng)以上代碼:
1)如果c為nil蝗砾,進(jìn)一步判斷block:如果block為false先较,那么直接返回false,表示未send數(shù)據(jù)悼粮;如果block為true闲勺,那么就讓當(dāng)前協(xié)程”永久“的阻塞在這個(gè)nil通道上;
2)如果block為false且closed為0扣猫,也就是在”不想阻塞“且通道”未關(guān)閉“的前提下菜循,如果是通道”無緩沖“且recvq為空,或者是通道”有緩沖“且緩沖已用盡申尤,則直接返回false癌幕。本步判斷是在不加鎖的情況下進(jìn)行的,目的是讓非阻塞寫在無法立即完成時(shí)能真正不阻塞(加鎖可能阻塞)昧穿。此處有疑問的話勺远,可返回上面看常規(guī)寫操作的情況分析;
3)加鎖;
4)如果closed不為0时鸵,即通道已經(jīng)關(guān)閉的話胶逢,則解鎖,然后panic饰潜。因?yàn)椴豢梢詫懭胍殃P(guān)閉的通道初坠;
5)如果recvq不為空,就從中取出第一個(gè)排隊(duì)的協(xié)程囊拜,將數(shù)據(jù)傳遞給這個(gè)協(xié)程,并將該協(xié)程置為ready狀態(tài)(放入run queue比搭,進(jìn)而得到調(diào)度)冠跷,然后解鎖,返回true身诺;
6)通過比較qcount和dataqsiz判斷緩沖區(qū)是否還有剩余空間蜜托,在這里”無緩沖“的通道被視為沒有剩余空間。有剩余空間的話霉赡,將數(shù)據(jù)追加到緩沖區(qū)中橄务,移動(dòng)sendx,增加qcount穴亏,解鎖蜂挪,返回true重挑;
7)如果block為false,即不想阻塞棠涮,則解鎖谬哀,返回false;
8)最后严肪,到達(dá)這里就是阻塞寫了史煎,當(dāng)前協(xié)程把自己追加到通道的sendq中阻塞排隊(duì),同時(shí)解鎖驳糯,等到條件滿足時(shí)會(huì)被喚醒篇梭。

流程比較長,還是畫個(gè)圖吧:


chansend.png

本篇總結(jié)

1)channel的常規(guī)寫操作如c <- x酝枢,會(huì)被編譯器轉(zhuǎn)換為對(duì)runtime.chansend1的調(diào)用恬偷,后者內(nèi)部只是調(diào)用了runtime.chansend;
2)非阻塞式的寫操作隧枫,即select喉磁、case、default三個(gè)一官脓,會(huì)被編譯器轉(zhuǎn)換為對(duì)runtime.selectnbsend的調(diào)用协怒,后者也僅僅是調(diào)用了runtime.chansend。非阻塞寫的實(shí)現(xiàn)效果如下:

select {
case c <- v:
    ... foo
default:
    ... bar
}

// 被編譯器轉(zhuǎn)化為:

if selectnbsend(c, v) {
    ... foo
} else {
    ... bar
}

上一篇:Golang channel 之 數(shù)據(jù)結(jié)構(gòu)
下一篇:Golang channel 之 讀操作 recv

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卑笨,一起剝皮案震驚了整個(gè)濱河市孕暇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赤兴,老刑警劉巖妖滔,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桶良,居然都是意外死亡座舍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門陨帆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曲秉,“玉大人,你說我怎么就攤上這事疲牵〕卸” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵纲爸,是天一觀的道長亥鸠。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么负蚊? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任神妹,我火速辦了婚禮,結(jié)果婚禮上盖桥,老公的妹妹穿的比我還像新娘灾螃。我一直安慰自己,他們只是感情好揩徊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布腰鬼。 她就那樣靜靜地躺著,像睡著了一般塑荒。 火紅的嫁衣襯著肌膚如雪熄赡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天齿税,我揣著相機(jī)與錄音彼硫,去河邊找鬼。 笑死凌箕,一個(gè)胖子當(dāng)著我的面吹牛拧篮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牵舱,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼串绩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芜壁?” 一聲冷哼從身側(cè)響起礁凡,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慧妄,沒想到半個(gè)月后顷牌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塞淹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年窟蓝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饱普。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡运挫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出费彼,到底是詐尸還是另有隱情滑臊,我是刑警寧澤口芍,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布箍铲,位于F島的核電站,受9級(jí)特大地震影響鬓椭,放射性物質(zhì)發(fā)生泄漏颠猴。R本人自食惡果不足惜关划,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翘瓮。 院中可真熱鬧贮折,春花似錦、人聲如沸资盅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呵扛。三九已至每庆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間今穿,已是汗流浹背缤灵。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蓝晒,地道東北人腮出。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像芝薇,于是被迫代替她去往敵國和親胚嘲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容