學(xué)習(xí)Go語(yǔ)言,就不得不學(xué)習(xí)協(xié)程(Goroutines)和信道(Channels),正是因?yàn)橛辛藚f(xié)程和信道的機(jī)制,才使得Go語(yǔ)言對(duì)高并發(fā)的天然支持,下面就讓我們對(duì)協(xié)程和信道的使用一探究竟吧!
Goroutines are functions or methods that run concurrently with other functions or methods. Goroutines can be thought of as light weight threads. The cost of creating a Goroutine is tiny when compared to a thread. Hence its common for Go applications to have thousands of Goroutines running concurrently.
從協(xié)程的概念中我們可以看出協(xié)程是一種函數(shù)或者一種方法,它們可以并發(fā)的運(yùn)行。協(xié)程可以被看作是輕量級(jí)的線(xiàn)程,但是與線(xiàn)程相比,創(chuàng)建一個(gè)協(xié)程的成本很低,因此在Go語(yǔ)言開(kāi)發(fā)的應(yīng)用中,經(jīng)常可以看到成千上萬(wàn)的協(xié)程并發(fā)執(zhí)行。
2. 如何使用協(xié)程
協(xié)程的使用十分簡(jiǎn)單,只需要在調(diào)用函數(shù)或者方法時(shí),在前面加上關(guān)鍵字go,就啟動(dòng)了一個(gè)新的Go協(xié)程(注意:主函數(shù)無(wú)需加關(guān)鍵字go,它默認(rèn)運(yùn)行在一個(gè)Go協(xié)程上,被稱(chēng)為Go主協(xié)程Main Goroutine),下面我們就通過(guò)示例演示如何創(chuàng)建一個(gè)協(xié)程:
package main
import (
"fmt"
)
func hello() {
fmt.Println("this is hello function")
}
func main() {
go hello()
fmt.Println("this is main function")
}
上述代碼中,我們?cè)谡{(diào)用hello()函數(shù)時(shí),對(duì)其加了go關(guān)鍵字,因此就創(chuàng)建了一個(gè)協(xié)程,是不是超級(jí)簡(jiǎn)單啊!是的,就是這么簡(jiǎn)單,但是當(dāng)你運(yùn)行程序之后,你會(huì)發(fā)現(xiàn)代碼只輸出了"this is main function",這是為什么呢?
這是因?yàn)镚o語(yǔ)言的協(xié)程有兩個(gè)重要的性質(zhì):
- 啟動(dòng)一個(gè)新的協(xié)程時(shí),協(xié)程的調(diào)用會(huì)立即返回。與函數(shù)不同,程序控制不會(huì)去等待 Go 協(xié)程執(zhí)行完畢。在調(diào)用 Go 協(xié)程之后,程序控制會(huì)立即返回到代碼的下一行,忽略該協(xié)程的任何返回值。
- 如果希望運(yùn)行其他 Go 協(xié)程,Go 主協(xié)程必須繼續(xù)運(yùn)行著。如果 Go 主協(xié)程終止,則程序終止,于是其他 Go 協(xié)程也不會(huì)繼續(xù)運(yùn)行。
這時(shí)你就會(huì)想,那么怎樣才能等待協(xié)程執(zhí)行完畢呢,各位大佬別急,下面就該輪到信道登場(chǎng)了。所以,拋開(kāi)信道談協(xié)程,就像是拋開(kāi)現(xiàn)實(shí)談理想,缺少腳踏實(shí)地做支撐,終究是竹籃打水一場(chǎng)空。
信道(Channels)
1. 什么是信道
Channels can be thought as pipes using which Goroutines communicate. Similar to how water flows from one end to another in a pipe, data can be sent from one end and received from the another end using channels.
信道可以看作是協(xié)程之間通信的管道,就好像管道中的水可以從一頭流動(dòng)到另一頭一樣,通過(guò)信道,數(shù)據(jù)也可以從一端發(fā)送,從另一端接收。
2. 信道使用的注意點(diǎn)
- 類(lèi)型:所有信道都關(guān)聯(lián)了一個(gè)類(lèi)型。信道只能運(yùn)輸這種類(lèi)型的數(shù)據(jù),而運(yùn)輸其他類(lèi)型的數(shù)據(jù)都是非法的;
- 定義:需要像對(duì) map 和切片所做的那樣,用 make 來(lái)定義信道;
- 零值:信道的零值為 nil;
- 發(fā)送和接收:信道旁的箭頭方向指定了是發(fā)送數(shù)據(jù)還是接收數(shù)據(jù),如果箭頭指向信道,則是把數(shù)據(jù)寫(xiě)入信道,反之則為從信道讀取數(shù)據(jù);
- 阻塞:信道的發(fā)送與接收默認(rèn)是阻塞的,即當(dāng)把數(shù)據(jù)發(fā)送到信道時(shí),程序控制會(huì)在發(fā)送數(shù)據(jù)的語(yǔ)句處發(fā)生阻塞,直到有其它Go協(xié)程從信道讀取到數(shù)據(jù),才會(huì)解除阻塞。與此類(lèi)似,當(dāng)讀取信道的數(shù)據(jù)時(shí),如果沒(méi)有其它的協(xié)程把數(shù)據(jù)寫(xiě)入到這個(gè)信道,那么讀取過(guò)程就會(huì)一直阻塞著;
- 單向信道:我們可以通過(guò)箭頭的指向來(lái)創(chuàng)建單向信道,這種信道只能發(fā)送或者接收數(shù)據(jù)。一個(gè)雙向信道可以轉(zhuǎn)換成唯送信道(Send Only)或者唯收信道(Receive Only),但是反過(guò)來(lái)不行;
- 死鎖:(對(duì)于雙向信道)當(dāng)Go協(xié)程給一個(gè)信道發(fā)送數(shù)據(jù)時(shí),需要有其他Go協(xié)程來(lái)接收數(shù)據(jù)。如果沒(méi)有的話(huà),程序就會(huì)在運(yùn)行時(shí)觸發(fā)panic,形成死鎖。同理,當(dāng)有Go協(xié)程等著從一個(gè)信道接收數(shù)據(jù)時(shí),必須要有其他的 Go協(xié)程向該信道寫(xiě)入數(shù)據(jù),要不然程序就會(huì)觸發(fā) panic;
- 關(guān)閉信道:使用close關(guān)閉信道;
- 遍歷信道:可以使用for range來(lái)遍歷信道;
- 緩沖信道:在用make函數(shù)創(chuàng)建信道時(shí),我們可以傳遞一個(gè)參數(shù)來(lái)指定緩沖的大小,如ch := make(chan type, capacity),默認(rèn)不填的情況下capacity的值為0,也就是所謂的無(wú)緩沖信道。對(duì)于緩沖信道,只在緩沖已滿(mǎn)的情況,才會(huì)阻塞向緩沖信道(Buffered Channel)發(fā)送數(shù)據(jù)。同樣,只有在緩沖為空的時(shí)候,才會(huì)阻塞從緩沖信道接收數(shù)據(jù)。
3. 信道的使用示例:
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(2 * time.Second)
}
}
上面的代碼中,我們創(chuàng)建了一個(gè)容量為2的緩沖信道,并將其作為參數(shù)傳給了write協(xié)程。write 協(xié)程有一個(gè) for 循環(huán),依次向信道 ch 寫(xiě)入 0~4。而緩沖信道的容量為 2,因此 write 協(xié)程里立即會(huì)向 ch 寫(xiě)入 0 和 1,接下來(lái)發(fā)生阻塞,直到 ch 內(nèi)的值被讀取。
主協(xié)程在休眠了兩秒后,使用 for range 循環(huán)讀取信道ch的值并打印,打印完之后又休眠兩秒。該過(guò)程會(huì)一直進(jìn)行,直到信道讀取完所有的值,并在 write 協(xié)程中關(guān)閉信道。最終代碼執(zhí)行輸出如下:
successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch
這就是Go語(yǔ)言協(xié)程和信道的簡(jiǎn)單使用方法,后面的文章中我們會(huì)學(xué)習(xí)它們的一些更加復(fù)雜的用法。
掃碼二維碼 獲取免費(fèi)視頻學(xué)習(xí)資料
- 本文固定鏈接: http://phpxs.com/post/7382/
- 轉(zhuǎn)載請(qǐng)注明:轉(zhuǎn)載必須在正文中標(biāo)注并保留原文鏈接
- 掃碼: 掃上方二維碼獲取免費(fèi)視頻資料