【Go并發(fā)基礎(chǔ)】

什么是并發(fā)

代碼按照順序執(zhí)行,即執(zhí)行完一句才會(huì)執(zhí)行下一句按价,這樣的代碼邏輯簡(jiǎn)單,符合人類的閱讀習(xí)慣。但是這樣是不夠的属韧,因?yàn)橛?jì)算機(jī)非常強(qiáng)大,比如一款音樂軟件蛤吓,在聽音樂的時(shí)候想下載歌曲宵喂,**同一時(shí)刻做兩件事,在編程中会傲,這就是并發(fā)**锅棕,并發(fā)可以讓編寫的程序在同一時(shí)刻做幾件事拙泽。

進(jìn)程和線程

講并發(fā)就繞不開線程,不過在介紹線程前裸燎,先介紹什么是進(jìn)程顾瞻。

**進(jìn)程**

在操作系統(tǒng)中,進(jìn)程是一個(gè)非常重要的概念德绿。當(dāng)啟動(dòng)一個(gè)軟件時(shí)荷荤,操作系統(tǒng)會(huì)為這個(gè)軟件創(chuàng)建一個(gè)進(jìn)程,這個(gè)進(jìn)程就是軟件的工作空間移稳,它包含了軟件運(yùn)行所需的所有資源蕴纳,比如內(nèi)存空間、文件句柄个粱,還有線程古毛。
image-20211213214435023
**線程**

線程是進(jìn)程對(duì)的執(zhí)行空間,一個(gè)進(jìn)程可以有多個(gè)線程几蜻,線程被操作系統(tǒng)調(diào)度執(zhí)行喇潘,比如下載一個(gè)文件,發(fā)送一個(gè)消息等梭稚。這種**多個(gè)線程被調(diào)度系統(tǒng)同時(shí)調(diào)度執(zhí)行的情況颖低,就是多線程并發(fā)**。

一個(gè)程序啟動(dòng)弧烤,就會(huì)有對(duì)應(yīng)的進(jìn)程被創(chuàng)建忱屑,同時(shí)進(jìn)程也會(huì)啟動(dòng)一個(gè)線程,這個(gè)線程就叫主線程暇昂。如果主線程結(jié)束莺戒,那么整個(gè)流程就退出了。有了主線程急波,就可以從主線程里啟動(dòng)其他線程从铲,也就有了多線程并發(fā)。

協(xié)程(Goroutine)

Go語言中**沒有線程的概念澄暮,只有協(xié)程名段,也稱為goroutine**。相比線程來說泣懊,協(xié)程更加輕量伸辟,一個(gè)程序可以隨意啟動(dòng)成千上萬個(gè)goroutine。

goroutine被Go Runtime調(diào)度馍刮,這一點(diǎn)和線程不一樣信夫。也即是說,Go語言的并發(fā)是由Go自己所調(diào)度的,自己決定同事執(zhí)行多少個(gè)goroutine静稻,什么時(shí)候執(zhí)行哪幾個(gè)警没。這對(duì)于開發(fā)者來說是完全透明,只需要在編碼的時(shí)候告訴Go語言要啟動(dòng)幾個(gè)goroutine姊扔,至于如何調(diào)度執(zhí)行惠奸,不用關(guān)心。

要啟動(dòng)一個(gè)goroutine非常簡(jiǎn)單恰梢,Go語言提供了 **go  關(guān)鍵字**,相比其他編程語言簡(jiǎn)化了很多
func main() {
    go fmt.Println("奔跑的蝸牛")
    fm.Println("獨(dú)臂阿童木")
    time.Sleep(time.Second)
}


// 輸出
獨(dú)臂阿童木
奔跑的蝸牛

// 程序是并發(fā)的梗掰,go關(guān)鍵字啟動(dòng)的goroutine并不阻塞main  goroutine的執(zhí)行嵌言,所以才會(huì)打印出上述結(jié)果。示例中main goroutine等待一秒及穗,避免main goroutine執(zhí)行完就退出了摧茴,看不到啟動(dòng)的新goroutine打印結(jié)果
這樣就啟動(dòng)了一個(gè)goroutine,用來調(diào)度fmt.Println()函數(shù)埂陆。這段代碼里有兩個(gè)goroutine苛白,一個(gè)是main函數(shù)啟動(dòng)的main  goroutine,一個(gè)是自己通過go關(guān)鍵字啟動(dòng)的goroutine。

**go 關(guān)鍵字** 緊跟一個(gè)方法或者函數(shù)焚虱,就可以啟動(dòng)一個(gè)goroutine购裙,讓方法在這啟動(dòng)的goroutine中運(yùn)行。

Channel

如果啟動(dòng)了多個(gè)goroutine鹃栽,他們之間如何通信呢躏率?這就是Go語言提供的 **Channel(通道)** 要解決的問題。

**聲明一個(gè) channel**

在Go語言中民鼓,聲明一個(gè)channel非常簡(jiǎn)單薇芝,使用內(nèi)置make函數(shù)即可
ch := make(chan string)

// chan 是關(guān)鍵字,表示是chan類型丰嘉,后面string表示channel里面的數(shù)據(jù)是string類型夯到。通過channel申明可以看到,chan是一個(gè)集合類型
定義好chan后就可以使用了饮亏,一個(gè)chan操作只有兩種:**發(fā)送和接收耍贾。**
  • 接收: 獲取chan中的值,操作符為 <-chan

  • 發(fā)送:向chan發(fā)送值克滴,把值放在chan中逼争,操作符為 chan <-

    ==小技巧==:注意發(fā)送和接收的操作符都是<-,只不過位置不同劝赔。接收的操作符在chan的左側(cè)誓焦,發(fā)送的操作符在chan的右側(cè)。

func main() {
    ch := make(chan string)
    
    go func() {
        fmt.Println("奔跑的蝸牛")
        ch <- "goroutine完成"
    }()
    
    fmt.Println("獨(dú)臂阿童木")
    v := <-ch
    fmt.Println("接收到的chan中的值:", v)
}

// 程序并沒有直接退出杂伟,可以看到打印結(jié)果移层,達(dá)到了time.Sleep函數(shù)的效果
// 因?yàn)橥ㄟ^maike創(chuàng)建的chan中沒有值,而main goroutine想從chan中獲得值赫粥,獲取不到就一直等待观话,等到另一個(gè)goroutine向chan中發(fā)送值為止

// 出書
獨(dú)臂阿童木
奔跑的蝸牛
接收到的chan中的值: goroutine完成
上述示例中,在新啟動(dòng)的goroutine中向chan類型的變量ch發(fā)送值越平;在main goroutine中频蛔,從變量ch接收值,如果ch中沒有值秦叛,則阻塞等待ch中有值可以接收為止晦溪。

channel有點(diǎn)像在兩個(gè)goroutine之間架起管道,一個(gè)goroutine可以往這個(gè)管道發(fā)送數(shù)據(jù)挣跋,另一個(gè)可以從這個(gè)管道接收數(shù)據(jù)三圆,有點(diǎn)類似隊(duì)列。

**無緩沖channel**

上述示例中make創(chuàng)建的就是一個(gè)無緩沖channel避咆,它的容量是0舟肉,不能存儲(chǔ)任何數(shù)據(jù)。所以無緩沖channel只起傳輸數(shù)據(jù)的作用查库,數(shù)據(jù)并不會(huì)在channel中停留路媚。這也意味著,**無緩沖channel的發(fā)送和接收操作是痛失進(jìn)行的**膨报,它也被稱為**同步channel**磷籍。

**有緩沖channel**

有緩沖channel類似一個(gè)阻塞對(duì)壘,內(nèi)部的元素先進(jìn)先出现柠。通過**make函數(shù)的第二個(gè)參數(shù)可以指定channel容量的大小院领,進(jìn)而創(chuàng)建一個(gè)有緩沖channel**
cacheCh := make(chan string, 3)

// 創(chuàng)建一個(gè)容量為5的channel,內(nèi)部元素類型是string够吩,也就是說這個(gè)channel內(nèi)部最多可以存放5個(gè)類型為string的元素
一個(gè)有緩沖channel具備一下特點(diǎn):

1. 有緩沖channel的內(nèi)部有一個(gè)緩沖隊(duì)列
2. 發(fā)送操作是向?qū)镜奈膊坎迦朐乇热唬绻?duì)列已滿,則阻塞等待周循,直到另一個(gè)goroutine執(zhí)行强法,接收操作釋放隊(duì)列空間
3. 接收操作是從隊(duì)列的頭部獲取元素并把它從隊(duì)列中刪除,如果隊(duì)列為空湾笛,則阻塞等待饮怯,知道另一個(gè)goroutine執(zhí)行,發(fā)送數(shù)據(jù)插入新的元素

因?yàn)橛芯彌_channel類似一個(gè)隊(duì)列嚎研,可以獲取它的容量和里面元素的個(gè)數(shù)蓖墅。
cacheCh := make(chan int, 5)
cachech <- 2
cachech <- 4
fmt.Println("cacheCh 容量為:", cap(cacheCh), "元素個(gè)數(shù)為:", len(cacheCh))

// 通過內(nèi)置函數(shù)cap可以獲取channel的容量,也就是最大能存放多少元素论矾,通過內(nèi)置函數(shù)len可以獲取channel中元素的個(gè)數(shù)
==小提示==:無緩沖channel其實(shí)就是一個(gè)容量大小為0的channel教翩。比如 **make(chan int, 0)**

**關(guān)閉 channel**

channel 可以使用內(nèi)置函數(shù) close 關(guān)閉。
close(cacheCh)
如果一個(gè)channel被關(guān)閉了贪壳,就不能向里面發(fā)送數(shù)據(jù)了饱亿,如果發(fā)送的話,會(huì)引起panic異常闰靴。但是還可以接收channel里的數(shù)據(jù)彪笼,如果channel里面沒有數(shù)據(jù)的話,接收的數(shù)據(jù)是元素類型的零值蚂且。

**單向channel**

有時(shí)候杰扫,因?yàn)樘厥獾臉I(yè)務(wù)需求,比如**限制一個(gè)channel只可以接收但不能發(fā)送膘掰,或者限制一個(gè)channel只能發(fā)送但不能接接收,這種channel被稱為單向channel**佳遣。

單向channel的聲明也很簡(jiǎn)單识埋,只需要在聲明的時(shí)候帶上<- 操作符即可。
onlySend := make(chan <- int)
onlyReceive := make(<-chan int)

// 注意: 聲明單向channel <- 操作符的位置和上面發(fā)送接收操作是一樣的零渐。
在函數(shù)后者方法的參數(shù)中窒舟,使用單向channel比較多,這樣可以防止一些操作影響了channel.
func counter(out chan <- int) {
    // 函數(shù)內(nèi)容使用變量out诵盼, 只能進(jìn)行發(fā)送操作
}
**select + channel示例**

假如我們要下載一個(gè)文件惠豺,啟動(dòng)了3個(gè)goroutine進(jìn)行下載,并發(fā)結(jié)果發(fā)送到3個(gè)channel中风宁。哪個(gè)先下載好洁墙,就先用哪個(gè)channel的結(jié)果。

在Go語言中戒财,通難過select語句實(shí)現(xiàn)多路復(fù)用操作热监,語句格式如下:
select {
case i1 <- c1 :
    // todo
case i2 <- c2 :
    // todo
default :
    // todo
}
整體結(jié)構(gòu)和switch很像,都有case和default饮寞,只不過select和case是一個(gè)個(gè)可以操作的channel孝扛。

==小提示==:多路復(fù)用可以簡(jiǎn)單的理解為,N個(gè)channel中幽崩,任意一個(gè)channel有數(shù)據(jù)產(chǎn)生苦始,select都可以監(jiān)聽到, 然后執(zhí)行對(duì)應(yīng)的分支慌申,接收數(shù)據(jù)并處理陌选。
// 多路下載示例

func main() {
    // 聲明3個(gè)存放結(jié)果的channel
    firstCh := make(chan string)
    secondCh := make(chan string)
    threeCh := make(chan string)
    
    // 同時(shí)開啟3個(gè)goroutine下載
    go func() {
        firstCh <- downloadFile("firstCh")  
    }()
     go func() {
        secondCh <- downloadFile("secondCh")  
    }()
     go func() {
        threeCh <- downloadFile("threeCh")  
    }()
    
    // 開啟多路復(fù)用,哪個(gè)channel能獲取到值,就先用哪個(gè)
    select {
        case filePath := <- firstCh :
        fmt.Println(filePath)
        case filePath := <- secoudCh :
        fmt.Println(filePath)
        case filePath := <- threeCh :
        fmt.Println(filePath)
    }
    
    func downloadFile(chanName string) string {
        // 模擬下載文件柠贤,可以隨機(jī)time.Sleep時(shí)間
        time.Sleep(time.Second)
        return chanName + "filePath"
    }
}
如果這些case中有一個(gè)可以執(zhí)行香浩,select語句會(huì)選擇該case語句執(zhí)行,如果同時(shí)又多個(gè)case可以被執(zhí)行臼勉,則隨機(jī)選擇一個(gè)邻吭,這樣每個(gè)case都有平等的被執(zhí)行的機(jī)會(huì)。如果一個(gè)select沒有任何case宴霸,那么就會(huì)一直等下去囱晴。

小結(jié):channel是Go語言并發(fā)的基礎(chǔ),在Go語言中瓢谢,提倡通過通信來共享內(nèi)存畸写,而不是通過共享內(nèi)存來通信,其實(shí)就是提倡通過channel發(fā)送接收消息的方式進(jìn)行數(shù)據(jù)通信氓扛,而不是通過修改同一個(gè)變量枯芬。所以在數(shù)據(jù)流動(dòng)、傳遞的場(chǎng)景中要優(yōu)先使用channel采郎,它是并發(fā)安全的千所,性能也不錯(cuò)。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒜埋,一起剝皮案震驚了整個(gè)濱河市淫痰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌整份,老刑警劉巖待错,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異烈评,居然都是意外死亡火俄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門础倍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烛占,“玉大人,你說我怎么就攤上這事沟启∫浼遥” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵德迹,是天一觀的道長(zhǎng)芽卿。 經(jīng)常有香客問我,道長(zhǎng)胳搞,這世上最難降的妖魔是什么卸例? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任称杨,我火速辦了婚禮,結(jié)果婚禮上筷转,老公的妹妹穿的比我還像新娘姑原。我一直安慰自己,他們只是感情好呜舒,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布锭汛。 她就那樣靜靜地躺著,像睡著了一般袭蝗。 火紅的嫁衣襯著肌膚如雪唤殴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天到腥,我揣著相機(jī)與錄音朵逝,去河邊找鬼。 笑死乡范,一個(gè)胖子當(dāng)著我的面吹牛配名,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晋辆,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼段誊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了栈拖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤没陡,失蹤者是張志新(化名)和其女友劉穎涩哟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盼玄,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贴彼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埃儿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片器仗。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖童番,靈堂內(nèi)的尸體忽然破棺而出精钮,到底是詐尸還是另有隱情,我是刑警寧澤剃斧,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布轨香,位于F島的核電站,受9級(jí)特大地震影響幼东,放射性物質(zhì)發(fā)生泄漏臂容。R本人自食惡果不足惜科雳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脓杉。 院中可真熱鬧糟秘,春花似錦、人聲如沸球散。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沛婴。三九已至吼畏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘁灯,已是汗流浹背泻蚊。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丑婿,地道東北人性雄。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像羹奉,于是被迫代替她去往敵國(guó)和親秒旋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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