Golang系列之Synchronization (四)

如何實(shí)現(xiàn)多線程之間的通信形娇,是并發(fā)模型里面最需要被考慮到的問題版姑。golang為此引進(jìn)了channel豁生,channel可作為goroutine之間交流的通道半等。每一個(gè)channel都可讀可寫屡立,且都是阻塞的洒忧,也即受神,當(dāng)一個(gè)goroutine讀一個(gè)channel的時(shí)候俺猿,就被阻塞住了碘举,直到另一個(gè)goroutine向這個(gè)channel寫入信息忘瓦。這個(gè)特性也常被用于synchronization。

聲明一個(gè)channel ,類型為int引颈。
c := make(chan int)
向channel寫入值
c <- 1
讀取channel的值并用于初始化a
a := <- c
另外有帶緩沖的channel:
buff := make(chan int, 10)
向buff寫入數(shù)據(jù)不會(huì)阻塞耕皮,但當(dāng)寫滿10個(gè)時(shí),就會(huì)被阻塞住蝙场,直到buff的數(shù)據(jù)被讀取明场,可視為一個(gè)帶長度限制的隊(duì)列。

channel的用處簡單明了李丰,任何需要線程之間交換傳遞數(shù)據(jù)的地方都可以用到channel苦锨。下面一個(gè)簡單的例子,最能說明channel的簡單和強(qiáng)大。

想象一個(gè)場景舟舒,若干只老鼠依次排開拉庶,最右邊的老鼠向它左邊的老鼠說一句話,左邊的老鼠聽到后秃励,又傳給它左邊的老鼠氏仗,直到最左邊的老鼠知道了這句話。這個(gè)過程可以用下面的代碼描述夺鲜。函數(shù)gopher代表一只老鼠皆尔,負(fù)責(zé)將右邊聽到的信息傳給左邊。在main函數(shù)的循環(huán)里面币励,定了次數(shù)1000000慷蠕,也就是有一百萬只老鼠參加了這個(gè)游戲,每次循環(huán)都make出新的channel食呻,最后流炕,向最右邊的channel寫入’i am hungry’(也就是left,因?yàn)樽詈髄eft = right)仅胞,并打印出最左邊(mostleft)收到的信息每辟。那么,完成整個(gè)過程需要多久呢干旧?在我的虛擬機(jī)里面(4g內(nèi)存渠欺,單核),一百萬只老鼠花費(fèi)的時(shí)間是12秒椎眯,下面的代碼編譯后直接可運(yùn)行的挠将,讀者可以試著調(diào)下循環(huán)的次數(shù)并觀察goroutine的增加對(duì)運(yùn)行時(shí)間和機(jī)器的影響。

package main
import (
     "time"
     "fmt"
)

func main() {
     tbeg := time.Now()
     mostleft := make(chan string)
     left := mostleft
     for i := 0;i < 1000000;i++ {
         right := make(chan string)
         go gopher(left, right)
          left = right
     }
     left <- "i am hungry"
     fmt.Println(<- mostleft)
     cost := time.Now().Sub(tbeg)
     fmt.Println(“cost: “, cost)
}

func gopher(left, right chan string) {
     left <- <- right   //所有的gopher均會(huì)被阻塞住直到最右邊收到消息
}

由上面的例子可以看出盅视,channel非常適合消息傳遞的場合,然而旦万,golang被廣為流傳的有一句話說到:Do not communicate by sharing memory; instead, share memory by communicating.所以闹击,channel應(yīng)當(dāng)替代Mutex?成艘?赏半?

我認(rèn)為,這句話最多只能算做golang宣傳的口號(hào)淆两,并不能當(dāng)成實(shí)踐真理断箫。并發(fā)模型的通信機(jī)制無非兩種,共享內(nèi)存和消息傳遞秋冰。channel只是作為消息傳遞的一種實(shí)現(xiàn)仲义,并不能說它就比共享內(nèi)存的做法更先進(jìn)或者簡潔。channel更合適數(shù)據(jù)傳遞收發(fā)的場景,mutex則適合共享數(shù)據(jù)讀取的場景埃撵。

func (t *Worker)loop(c chan string) {
     for {
         select {
             case s := <- c:
                   t.doSomething(s)
             case <- t.stop:
                   break
         }
     }
}

上面的例子赵颅,守護(hù)函數(shù)從channel s 或者channel t.stop里面獲取消息,select語句用于從多個(gè)channel里面選取其一暂刘,當(dāng)某個(gè)channel準(zhǔn)備好的時(shí)候饺谬,就跳到那個(gè)對(duì)應(yīng)的case。loop函數(shù)傾聽著兩個(gè)數(shù)據(jù)來源谣拣,收到數(shù)據(jù)后進(jìn)行處理募寨,當(dāng)t.stop收到消息時(shí),則退出森缠。這種數(shù)據(jù)傳遞收發(fā)的場景拔鹰,用起channel來就簡潔明了,如果是Mutex的話辅鲸,則要不斷加鎖->判斷數(shù)據(jù)是否準(zhǔn)備好->解鎖->sleep等待格郁,很是麻煩。

而在另外的場景中独悴,則用Mutex最好不過了

// goroutine 1
func update() {
     lock.Lock()
     html = XXX
     lock.Unlock()
}
// goroutine 2
func handler(r Request, w writer) {
     lock.Lock()
     w.write(html)
     lock.Unlock()
}

handler是一個(gè)網(wǎng)頁訪問的處理接口例书,當(dāng)收到一個(gè)請(qǐng)求的時(shí)候,負(fù)責(zé)返回html刻炒,而html的內(nèi)容會(huì)時(shí)而更新决采,由update函數(shù)進(jìn)行處理。這種場景下坟奥,用鎖是再好不過了树瞭,這時(shí)候非要share memory by communicating的話也不是不可以,只是會(huì)平添復(fù)雜的同步邏輯爱谁,讀者不妨嘗試一下晒喷,嘻嘻。

講道理访敌,golang對(duì)channel和mutex都支持得很好凉敲,所以無謂去爭論哪個(gè)更好,哪種用起來簡潔就用哪種寺旺,沒必要拘束于其中之一爷抓。畢竟白帽黑貓,能最快抓到老鼠的就是更好的貓阻塑。

前些日子蓝撇,一個(gè)沒注意在項(xiàng)目里弄了一個(gè)bug,自己檢查檢查不出來陈莽,最后才知道該加鎖的地方忘記加鎖了渤昌。Don’t be clever虽抄,是The Go Memory Model里給的忠告。下面這段代碼就是引發(fā)bug的地方耘沼,在這里列出代碼邏輯极颓,既說明下golang語法上一些特性,畢竟talk is cheap群嗤,show me the code : )菠隆。也借此尋求下讀者的意見,是否有更好的方法來重構(gòu)這段代碼狂秘,交流交流骇径。

// 需求:有規(guī)則集合R,數(shù)據(jù)庫D者春,每一條規(guī)則r需到D拿數(shù)據(jù)破衔,并判斷此條規(guī)則是否已經(jīng)符合。
// 要求:因?yàn)镽量比較大钱烟,且經(jīng)常變化晰筛,需要程序作為daemon循環(huán)的跑,實(shí)時(shí)性要求比較高拴袭,所以每跑一次耗費(fèi)的時(shí)間不能太長读第。
// 最簡單的處理辦法就是,從R一條條取出規(guī)則拥刻,然后一條條到D拿數(shù)據(jù)比對(duì)怜瞒,邏輯非常簡單,
// 但是般哼,這樣吴汪,每一萬條規(guī)則耗費(fèi)的時(shí)間 > 10 min。不符合要求蒸眠。
// 所以需要將規(guī)則聚合漾橙,將相似規(guī)則聚合成一條查詢sql到D取數(shù)據(jù),然后返回楞卡∷耍可是聚合怎么聚合呢?
// 每一條規(guī)則都有很多屬性臀晃,如果在主邏輯里面進(jìn)行聚合觉渴,將會(huì)使代碼不清晰介劫,且若以后增加規(guī)則屬性徽惋,
// 整個(gè)規(guī)則分類邏輯都要改。所以最好主邏輯還是一條條拿規(guī)則座韵,一條條取數(shù)據(jù)進(jìn)行判定险绘,這樣代碼會(huì)清晰很多踢京。
// 在這種方法下,每一萬條處理時(shí)間 < 4s

//被循環(huán)調(diào)用的函數(shù)宦棺,主邏輯
func Work() {
     result := []Result{}
     wait := sync.WaitGroup{}
     for r := range R {
          wait.Add(1)
          //異步IO瓣距,輸入一條規(guī)則,返回對(duì)應(yīng)的數(shù)據(jù)代咸,然后在callback里面進(jìn)行判斷是否規(guī)則已符合
          //NodeJS借鑒來的其實(shí)蹈丸,想一條進(jìn),一條出呐芥,而又想按規(guī)則聚合到數(shù)據(jù)庫拿數(shù)據(jù)逻杖,只想到這種方法了。
          Select(r,func(r Rule, d Data){
              result = append(result, r.Judge(d)) //這里沒加鎖會(huì)導(dǎo)致race conditions
              wait.Done()
          })
     }
     Do()
     wait.Wait()  //等待所有callback都被執(zhí)行了
     doSomething(result)
}

//Select的實(shí)現(xiàn)封裝思瘟,這里可以一條條處理荸百,也可以聚合后再處理,已經(jīng)對(duì)外隱藏了滨攻。
func Select(r Rule, f Callback) {
     count++
     Class.add(r)    //按規(guī)則屬性組合哈希值進(jìn)行聚合够话。這里再怎么復(fù)雜都沒關(guān)系了。
     go func() {
         <- done     //必須等待Do()函數(shù)被執(zhí)行光绕,這樣getData()才能拿到數(shù)據(jù)女嘲。
         f(r, getData(r))
     }
}

func Do() {
     //do dirty work
     //按聚合的規(guī)則到D拿數(shù)據(jù)
     ...
     ...
     //通知所有Select調(diào)用執(zhí)行callback
     for i := 0;i < count;i++ {
          done <- true
     }
}

原文轉(zhuǎn)自謝培陽的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奇钞,隨后出現(xiàn)的幾起案子澡为,更是在濱河造成了極大的恐慌,老刑警劉巖景埃,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媒至,死亡現(xiàn)場離奇詭異,居然都是意外死亡谷徙,警方通過查閱死者的電腦和手機(jī)拒啰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來完慧,“玉大人谋旦,你說我怎么就攤上這事∏幔” “怎么了册着?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脾歧。 經(jīng)常有香客問我甲捏,道長,這世上最難降的妖魔是什么鞭执? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任司顿,我火速辦了婚禮芒粹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘大溜。我一直安慰自己化漆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布钦奋。 她就那樣靜靜地躺著座云,像睡著了一般。 火紅的嫁衣襯著肌膚如雪付材。 梳的紋絲不亂的頭發(fā)上疙教,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音伞租,去河邊找鬼贞谓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛葵诈,可吹牛的內(nèi)容都是我干的裸弦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼作喘,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼理疙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泞坦,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤窖贤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后贰锁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赃梧,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年豌熄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了授嘀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锣险,死狀恐怖蹄皱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芯肤,我是刑警寧澤巷折,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站崖咨,受9級(jí)特大地震影響锻拘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掩幢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一逊拍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧际邻,春花似錦芯丧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轮听,卻和暖如春骗露,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背血巍。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工萧锉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人述寡。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓柿隙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鲫凶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子禀崖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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

  • 能力模型 選擇題 [primary] 下面屬于關(guān)鍵字的是()A. funcB. defC. structD. cl...
    _張曉龍_閱讀 24,835評(píng)論 14 224
  • 控制并發(fā)有三種種經(jīng)典的方式,一種是通過channel通知實(shí)現(xiàn)并發(fā)控制 一種是WaitGroup螟炫,另外一種就是Con...
    wiseAaron閱讀 10,655評(píng)論 4 34
  • Goroutines 模型:和其他goroutine在共享的地址空間中并發(fā)執(zhí)行的函數(shù) 資源消耗: 初始時(shí)非常小的棧...
    大漠狼道閱讀 1,285評(píng)論 0 8
  • 今天介紹一下 go語言的并發(fā)機(jī)制以及它所使用的CSP并發(fā)模型 CSP并發(fā)模型 CSP模型是上個(gè)世紀(jì)七十年代提出的昼钻,...
    falm閱讀 68,463評(píng)論 10 80
  • go語言的并發(fā)機(jī)制以及它所使用的CSP并發(fā)模型 CSP并發(fā)模型CSP模型是上個(gè)世紀(jì)七十年代提出的掸屡,用于描述兩個(gè)獨(dú)立...
    seven_son閱讀 2,601評(píng)論 0 5