go的強(qiáng)大并發(fā)

[TOC]

go的強(qiáng)大并發(fā)

在go語言中梆奈,go routine是并發(fā)的基本單位,是操作系統(tǒng)中提到的用戶級線程侦锯,輕量線程液兽,它的執(zhí)行切換不會(huì)觸發(fā)操作系統(tǒng)內(nèi)核態(tài)的轉(zhuǎn)換。channel儒老,mutex蝴乔,是go中進(jìn)行協(xié)程間 協(xié)調(diào),通信驮樊,以及控制競爭訪問 的重要機(jī)制薇正,程序間的通信一般就兩種:共享內(nèi)存和消息傳遞

Share memory by communicating, don’t communicate by sharing memory.
通過通信共享內(nèi)存,而不是通過共享內(nèi)存而通信囚衔。

線程挖腰,線程的閱讀

鎖和信號量的閱讀

神奇的channel

channel可以寫入,可以從中讀出练湿,并且是并發(fā)安全的猴仑,即多個(gè)routine同時(shí)讀寫是不會(huì)出現(xiàn)問題的。相當(dāng)于用于同步和通信的有鎖隊(duì)列

CSP 是 Communicating Sequential Process 的簡稱肥哎,中文可以叫做通信順序進(jìn)程辽俗,是一種并發(fā)編程模型,由 Tony Hoare 于 1977 年提出贤姆。簡單來說榆苞,CSP 模型由并發(fā)執(zhí)行的實(shí)體(線程或者進(jìn)程)所組成,實(shí)體之間通過發(fā)送消息進(jìn)行通信霞捡,這里發(fā)送消息時(shí)使用的就是通道坐漏,或者叫 channel。CSP 模型的關(guān)鍵是關(guān)注 channel碧信,而不關(guān)注發(fā)送消息的實(shí)體赊琳。Go 語言實(shí)現(xiàn)了 CSP 部分理論,goroutine 對應(yīng) CSP 中并發(fā)執(zhí)行的實(shí)體砰碴,channel 也就對應(yīng)著 CSP 中的 channel躏筏。

  • 無緩存的channel
  • 有緩存的channel

無緩存的channel的讀寫如果沒有對應(yīng)的反操作被執(zhí)行,會(huì)被阻塞呈枉。

Notice

不可以理解為是size為1的有緩沖channel趁尼,因?yàn)?. size 為1 且buf沒滿時(shí)候的發(fā)送或接收操作埃碱,若沒有反操作,不會(huì)阻塞 2. 可見性規(guī)則酥泞。

那是否可以理解為size=0砚殿,因?yàn)閟ize為0。 它的操作芝囤,若沒有反操作似炎,會(huì)阻塞。

根據(jù) 《可見性規(guī)范描述》第四條:The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes. 則當(dāng)buffered的size為0悯姊, 第k條接收操作先于第k條發(fā)送操作的完成羡藐。等同于規(guī)則三。

有緩存的channel在緩存沒空悯许,或者沒滿之前仆嗦,讀或者寫都不會(huì)阻塞。

Notice

不要在一個(gè)goroutine中對同一個(gè)channel執(zhí)行發(fā)送和接收操作

Make(chan)不帶箭頭時(shí)先壕,通道是沒有方向的欧啤,但當(dāng)它被賦予的靜態(tài)類型是有方向的時(shí)候:當(dāng)一個(gè)通道被賦予給一個(gè)靜態(tài)類型為<-chan的變量時(shí),goroutine只能從中接收启上,又稱為接收通道邢隧;被賦予給了 chan <- 變量時(shí),goroutine只能向它發(fā)送值冈在,又稱為發(fā)送channel倒慧。

Make(chan)的時(shí)候可以帶方向。

對于channel中發(fā)送和接收出去的都會(huì)是復(fù)制的值包券,不是原值纫谅。

一個(gè)channel的變量運(yùn)行中可以有三種狀態(tài):

  1. nil
  2. 開啟著的
  3. 關(guān)閉了的

nil channel

對一個(gè)nil的channel讀寫會(huì)被阻塞,當(dāng)用在select語句中時(shí)溅固,會(huì)被強(qiáng)制忽略付秕。當(dāng)for-select中讀到被關(guān)閉的channel時(shí),即生產(chǎn)者侍郭,關(guān)閉了channel询吴,這樣在下次的循環(huán)中就不會(huì)從中讀取到值了,因?yàn)閷τ陉P(guān)閉了的channel亮元,總是讀到對應(yīng)類型的零值猛计。

for {
    select {
     case v, ok<- 被關(guān)閉了的channel的變量 variable:
    if ok {dov} else { 將被關(guān)閉的channel的variable置為 nil}
   case v <- 其他channel 
    }
}

開啟著的channel

對開啟的channel的讀寫,是否阻塞取決于是否還有空的緩存位爆捞。

關(guān)閉著的channel

  1. 從一個(gè)關(guān)閉的channel接收奉瘤,永遠(yuǎn) 不會(huì)阻塞,立刻會(huì)讀到channel內(nèi)類型的零值煮甥。
  2. 而對一個(gè)關(guān)閉了channel發(fā)送盗温,會(huì)panic藕赞。
  3. 并且重復(fù)關(guān)閉一個(gè)已經(jīng)關(guān)閉了的channel也會(huì)panic
  4. 關(guān)閉一個(gè)沒有了緩存的channel(或者本身就是無緩存channel)卖局, 所有阻塞在 channel接收操作上的阻塞會(huì)立刻返回零值找默。

另外

close 內(nèi)置函數(shù)關(guān)閉一個(gè)通道,該通道必須是雙向的或僅發(fā)送的吼驶。
close(make(<-chan int, 10 )) 這個(gè)close會(huì)報(bào)錯(cuò), 因?yàn)槭莻€(gè)接收channel店煞, goroutine從中接收元素
channel 的關(guān)閉應(yīng)僅由發(fā)送方執(zhí)行蟹演,而不應(yīng)由接收方執(zhí)行,并且在收到最后發(fā)送的值后具有關(guān)閉通道的效果顷蟀。
即goroutine在沒有信息需要發(fā)送到channel的時(shí)候酒请,就關(guān)閉它

因?yàn)檫@幾個(gè)特點(diǎn),所以在channel必須被優(yōu)雅關(guān)閉鸣个,另外可以利用它們做一些特殊的操作羞反,如:

  • 通過4,我們可以實(shí)現(xiàn)結(jié)束再通知
  • 因?yàn)?囤萤,我們在 for-select中必須做好判斷:是否關(guān)閉昼窗,然后如何避免再次讀到,是否可以置為nil(有時(shí)候不能置為nil涛舍,這個(gè)時(shí)候貌似都可以用for-range, 它的特殊在于會(huì)自己判斷close然后跳出來)

通道的關(guān)閉原則

關(guān)于channel的一些關(guān)閉的資料, 雖然我并不完全認(rèn)同澄惊,但是也有可取之處,比如:

  • 利用生產(chǎn)者(goroutine)和消費(fèi)者(goroutine)的各類場景來闡述channel的正確用法

  • 對關(guān)閉channel的執(zhí)著富雅,與細(xì)致入微的分析思考:一定要關(guān)閉channel以避免死鎖

  • 因?yàn)橄M(fèi)者的意外退出(一個(gè)存活的都沒了)而導(dǎo)致生產(chǎn)者的發(fā)送channel阻塞掸驱,而導(dǎo)致了死鎖,是死鎖嗎没佑?這不是goroutine泄露嗎毕贼?通過defer強(qiáng)行關(guān)閉來自生產(chǎn)者的發(fā)送channel(必須是發(fā)送或雙向的),然后讓生產(chǎn)者因?yàn)榈诙€(gè)特點(diǎn), 而panic蛤奢,再利用defer處理這個(gè)異常鬼癣,是的生產(chǎn)者退出。

  • 消費(fèi)者(只有一個(gè))通過多加一個(gè)channel來通知生產(chǎn)者啤贩,主動(dòng)讓它停止生產(chǎn):向取消channel中發(fā)送一個(gè)值扣溺,或者關(guān)閉它,單個(gè)的生產(chǎn)者接收到一個(gè)值瓜晤,主動(dòng)關(guān)閉生產(chǎn)chan和取消chan锥余。多個(gè)生產(chǎn)者接收到關(guān)閉信號,主動(dòng)關(guān)閉生產(chǎn)痢掠。小問題: 生產(chǎn)者已經(jīng)生產(chǎn)的值驱犹,在chan中了的嘲恍,可以消費(fèi)亦可以不消費(fèi),看選擇了雄驹。

  • 上面的情況佃牛,如果消費(fèi)者有多個(gè)呢,同時(shí)要求停止生產(chǎn)医舆,即 n-m 的消費(fèi)與生產(chǎn)俘侠,有什么好的通用方法?每個(gè)都關(guān)閉取消chan蔬将,同時(shí)都要做deferrecover

  • 還是上面的情況爷速,若是生產(chǎn)者已經(jīng)生產(chǎn)了的,雖然還沒放到chan中的霞怀,如果想繼續(xù)出來怎么辦惫东,那就在檢測在取消chan的關(guān)閉后,繼續(xù)發(fā)送(可以主動(dòng)置為nil或者在檢測關(guān)閉后發(fā)送)并且消費(fèi)側(cè)也要工作毙石。

  • 如果消費(fèi)者廉沮,在消費(fèi)結(jié)束以后(即生產(chǎn)者關(guān)閉了),所有的消費(fèi)者消費(fèi)完了生產(chǎn)者的生產(chǎn)徐矩,可以用計(jì)數(shù)器柵欄WaitGroup來統(tǒng)計(jì)狀態(tài)(柵欄的+1決不能放在子協(xié)程內(nèi))

在設(shè)計(jì)上滞时,不管是消費(fèi)者與生產(chǎn)者: 1-1, 1-n滤灯,n-1漂洋,n-m,都要避免channel的重復(fù)關(guān)閉力喷,如果有一定要defer recover刽漂。且總有一個(gè)routine知道發(fā)送已經(jīng)完成了,或者都知道弟孟;總有一個(gè)routine知道接收結(jié)束了贝咙,或者都知道,即接收的channel被發(fā)送方關(guān)閉了拂募,接下里的操作是每個(gè)routine都做庭猩,還是統(tǒng)一一起做,用柵欄陈症。一定要注意蔼水,如果難以避免重復(fù)關(guān)閉,無法關(guān)閉录肯,那一定是設(shè)計(jì)上有問題趴腋,并且很可能routine泄露, 殘留數(shù)據(jù)為發(fā)送,未處理,未接受等优炬。

疑問:

如果消費(fèi)者被退出颁井,生產(chǎn)者也停止,并且關(guān)閉了它們的channel蠢护,但是這個(gè)channel的緩存中還有值雅宾,這個(gè)算什么(大概其實(shí)是上面第四點(diǎn))?泄露葵硕?會(huì)被回收嗎眉抬?

答:

一個(gè)channel,無論是否被關(guān)閉懈凹,只要沒有再被引用蜀变,就會(huì)被go語言的垃圾回收器自動(dòng)回收,即盧軒認(rèn)為:channel不需要通過close來確保被回收蘸劈,只有當(dāng)需要告訴channel的接收者,不會(huì)再有數(shù)據(jù)發(fā)送過去了尊沸,才會(huì)需要關(guān)閉channel威沫。

并發(fā)模式

流水線模型

模型來自現(xiàn)實(shí)世界的啟發(fā),是對現(xiàn)實(shí)世界的生產(chǎn)消費(fèi)洼专,通知發(fā)布等模式的抽象棒掠,模仿。

流水線由多個(gè)階段組成屁商,階段間用channel鏈接烟很,每個(gè)階段可以由同時(shí)運(yùn)行的多個(gè)go routine組成。這里也可以看出go并發(fā)的思想蜡镶,通過通信來共享數(shù)據(jù)雾袱,內(nèi)存,信息官还,控制等芹橡。

  • Fan-In: 扇入,一個(gè)goroutine從多個(gè)channel讀取數(shù)據(jù)望伦,直到它們都關(guān)閉林说,In,代表一種收斂的模式屯伞,常用來做結(jié)果的收集腿箩。
  • Fan-Out:扇出,多個(gè)goroutine 從一個(gè)channel讀取數(shù)據(jù)劣摇,知道它關(guān)閉珠移,Out,代表一種發(fā)散的模式,常用來做任務(wù)的分發(fā)剑梳。

將Fan-In和Fan-out結(jié)合起來唆貌,可以實(shí)現(xiàn)豐富且強(qiáng)大的流水線模型。流水線模型的channel通常是有緩存的垢乙,這樣能搞適當(dāng)提高程序性能锨咙。

Sync包:mutex

channel的是go語言的主角,但是也有它不能完美解決的場景追逮,mutex就可以彌補(bǔ)酪刀,且channel的開銷其實(shí)是大于mutex的,并且從結(jié)構(gòu)來看钮孵,channel有鎖也有cond骂倘。

解決問題的對比

channel

DataFlow -> Drawing -> Pipieline -> Exiting

數(shù)據(jù)是流動(dòng)的,可以Drawing將其畫出來, pipeline就是流水線巴席, 然后退出历涝。所以它適合的點(diǎn)在于:數(shù)據(jù)的流動(dòng)

  1. 傳遞數(shù)據(jù)的所有權(quán),將數(shù)據(jù)發(fā)給其他協(xié)程
  2. 分發(fā)任務(wù)漾唉,任務(wù)就是數(shù)據(jù)
  3. 交流異步結(jié)果荧库,結(jié)果是數(shù)據(jù)

mutex

適合不動(dòng)(不移動(dòng))的數(shù)據(jù), 1. 緩存 2. 狀態(tài)

這兩個(gè)該如何理解赵刑?分衫??更新緩存般此?改狀態(tài)蚪战?

不同的goroutine之間不再像合作,而像是競爭铐懊。對臨界區(qū)資源的讀取或者修改邀桑。

sync.Once

= 原子操作配合互斥鎖 , 但是開銷比 原子操作 + 互斥鎖小科乎,atomic包對基本類型和復(fù)雜對象都提供了原子操作的支持概漱。

LoadStore方法,分別來加載和保存數(shù)據(jù)喜喂,返回值都是interface{}瓤摧,所以可以用于任何復(fù)雜自定義類型。所謂原子操作玉吁,就是無論任何適合讀照弥,都不會(huì)讀到一半的數(shù)據(jù),在jvm的并發(fā)中进副,使用不當(dāng)?shù)臅r(shí)候这揣,可以讀到初始化了一半的對象等悔常,是"破損的數(shù)據(jù)"。

var {
  instance *Singleton // 某單例给赞, 非導(dǎo)出
  once sync.Once
}

func Instance() *Singleton { // 導(dǎo)出机打,單例的獲取方法
  once.Do(func() {instance = &singleton{}})
  return instance
}

cond
mutex
Pool
Map
WaitGroup
RWMutex

channel,mutex片迅,waitGroup

channe和mutex并不是相互對立的關(guān)系残邀,而是互補(bǔ),在某些場景我們選擇 channnel or mutex , 復(fù)雜場景是channel and mutex柑蛇,

甚至再加上WaitGroup等待柵欄芥挣,流動(dòng)的數(shù)據(jù)在被處理完后,在某個(gè)點(diǎn)不動(dòng)耻台,等待其他處理的結(jié)束空免,這是BSP

可見性

由于現(xiàn)代計(jì)算機(jī)cpu的架構(gòu)設(shè)計(jì)和實(shí)現(xiàn)盆耽,多核運(yùn)算的線程或者程序總是會(huì)遇到可見性問題蹋砚,這個(gè)問題在go中也存在,雖然go用的是協(xié)程摄杂,對于可見性坝咐,官方文檔。另外根據(jù)規(guī)范匙姜,main函數(shù)的執(zhí)行不會(huì)等待其他routine的運(yùn)行結(jié)束畅厢。當(dāng)初始化的時(shí)候冯痢,會(huì)按照main包里導(dǎo)入的其他包順序不斷深入導(dǎo)入并且初始化氮昧,是個(gè)遞歸的過程。在main.main函數(shù)執(zhí)行之前所有代碼都運(yùn)行在同一個(gè)Goroutine中浦楣,也是運(yùn)行在程序的主系統(tǒng)線程中袖肥。如果某個(gè)init函數(shù)內(nèi)部用go關(guān)鍵字啟動(dòng)了新的Goroutine的話,新的Goroutine和main.main函數(shù)是并發(fā)執(zhí)行的振劳。

中文閱讀材料:(https://juejin.cn/post/6911126210340716558#heading-7)

常見并發(fā)模式

在神奇的channel中椎组,有一些直觀的對并發(fā)模式的思考。

  • 消費(fèi)者和生產(chǎn)者:生產(chǎn)者的生產(chǎn)發(fā)給了消費(fèi)者历恐,相互了解

  • 發(fā)布訂閱模式:發(fā)布者不關(guān)心誰在訂閱寸癌,它只是發(fā)布到了通道,由中間人弱贼,將通道的消息送給訂閱者

并發(fā)度的控制

我們利用channel來傳遞蒸苇,通信,也可以利用阻塞 + 緩存 來實(shí)現(xiàn)并發(fā)度的控制吮旅。緩存 + 阻塞 = 帶一定數(shù)據(jù)的可阻塞可恢復(fù)的信號量溪烤。

啟動(dòng)goroutine之前或者之中,操作緩存channel,等待channel不能操作的時(shí)候檬嘀,goroutine的創(chuàng)建或者創(chuàng)建好的運(yùn)行中會(huì)阻塞槽驶,再每個(gè)運(yùn)行結(jié)束的channel會(huì)反操作channel,然后執(zhí)行流會(huì)恢復(fù)鸳兽。

利用這個(gè)方法掂铐,1. 可以控制并發(fā)度 2. 可以檢測channel的緩存的空余來判斷程序的運(yùn)行狀態(tài),通過已使用和空的位置比例判斷并發(fā)率贸铜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堡纬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蒿秦,更是在濱河造成了極大的恐慌烤镐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棍鳖,死亡現(xiàn)場離奇詭異炮叶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)渡处,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門镜悉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人医瘫,你說我怎么就攤上這事侣肄。” “怎么了醇份?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵稼锅,是天一觀的道長。 經(jīng)常有香客問我僚纷,道長矩距,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任怖竭,我火速辦了婚禮锥债,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痊臭。我一直安慰自己哮肚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布广匙。 她就那樣靜靜地躺著允趟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艇潭。 梳的紋絲不亂的頭發(fā)上拼窥,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天戏蔑,我揣著相機(jī)與錄音,去河邊找鬼鲁纠。 笑死总棵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的改含。 我是一名探鬼主播情龄,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捍壤!你這毒婦竟也來了骤视?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鹃觉,失蹤者是張志新(化名)和其女友劉穎专酗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盗扇,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祷肯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疗隶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佑笋。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖斑鼻,靈堂內(nèi)的尸體忽然破棺而出蒋纬,到底是詐尸還是另有隱情,我是刑警寧澤坚弱,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布蜀备,位于F島的核電站,受9級特大地震影響史汗,放射性物質(zhì)發(fā)生泄漏琼掠。R本人自食惡果不足惜拒垃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一停撞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悼瓮,春花似錦戈毒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至命贴,卻和暖如春道宅,著一層夾襖步出監(jiān)牢的瞬間食听,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工污茵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留樱报,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓泞当,卻偏偏與公主長得像迹蛤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子襟士,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

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

  • 本文從上下文Context盗飒、同步原語與鎖、Channel陋桂、調(diào)度器四個(gè)方面介紹Go語言是如何實(shí)現(xiàn)并發(fā)的逆趣。本文絕大部分...
    彥幀閱讀 1,568評論 1 3
  • 在講 channel 之前,有必要先提一下 CSP 模型嗜历,傳統(tǒng)的并發(fā)模型主要分為 Actor模型和CSP模型汗贫,CS...
    鄒志全閱讀 392評論 0 2
  • 面向并發(fā)的內(nèi)存模型 在早期,CPU都是以單核的形式順序執(zhí)行機(jī)器指令秸脱。Go語言的祖先C語言正是這種順序編程語言的代表...
    Gundy_閱讀 603評論 0 3
  • 系統(tǒng)文件介紹 在程序啟動(dòng)運(yùn)行時(shí)落包,自動(dòng)打開,運(yùn)行結(jié)束摊唇,自動(dòng)關(guān)閉咐蝇。 鍵盤(硬件)—— 標(biāo)準(zhǔn)輸入(文件)stdin —...
    泡泡龍吐泡泡閱讀 4,980評論 0 2
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭巷查,有人歡樂有人憂愁有序,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評論 28 53