上一篇文章文章主要學(xué)習(xí)了Go語言中的接口、反射以及錯誤和異常處理。本篇文章主要學(xué)習(xí)Go語言的協(xié)程,當(dāng)然也是GO語言基礎(chǔ)的最后一篇。
goroutine:
goroutine是Go并行設(shè)計(jì)的核心,也是這門語言的精髓體現(xiàn)。goroutine這個關(guān)鍵字就是協(xié)程,但是它比線程更小。說起線程,大家可能都不陌生。線程,是程序執(zhí)行的最小單元。一個標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針,寄存器集合和堆棧組成。另外,線程是進(jìn)程中的一個實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。
現(xiàn)在Go語言運(yùn)用協(xié)程這一比線程更小的執(zhí)行單元,十幾個goroutine可能體現(xiàn)在底層就是五六個線程,Go語言內(nèi)部幫你實(shí)現(xiàn)了這些goroutine之間的內(nèi)存共享。執(zhí)行g(shù)oroutine只需極少的棧內(nèi)存(大概是4~5KB),當(dāng)然會根據(jù)相應(yīng)的數(shù)據(jù)伸縮。也正因?yàn)槿绱耍赏瑫r運(yùn)行成千上萬個并發(fā)任務(wù)。goroutine比thread更易用、更高效、更輕便。
goroutine是通過Go的runtime管理的一個線程管理器。goroutine的作用就是一個普通的函數(shù)。以下是協(xié)程的寫法以及結(jié)果測試:



理論上來說:多個goroutine運(yùn)行在同一個進(jìn)程里面,共享內(nèi)存數(shù)據(jù),不過設(shè)計(jì)上我們要遵循:不要通過共享來通信,而要通過通信來共享。goroutine運(yùn)行在相同的地址空間,因此訪問共享內(nèi)存必須做好同步。那么goroutine之間如何進(jìn)行數(shù)據(jù)的通信呢,Go提供了一個很好的通信機(jī)制:channel。
channel是Go中的一個核心類型,你可以把它看成一個管道,通過它并發(fā)核心單元就可以發(fā)送或者接收數(shù)據(jù)進(jìn)行通訊。
首先,channel必須先創(chuàng)建再使用,另外它的操作符是箭頭 <-
Channel類型的定義格式(有三種寫法)如下:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) 數(shù)據(jù)類型
其中,這里的 <- 代表的是channel方向。如果沒有指定方向,那么Channel就是雙向,這樣就意味可以接收數(shù)據(jù),也可以發(fā)送數(shù)據(jù)。
(前面說了可以把channel看成一個管道,既然是管道那么就有流向)。也就是下面三種寫法:
chan T? ? ? ? ? // 可以接收和發(fā)送類型為 T 的數(shù)據(jù)
chan<- float64? // 只可以用來發(fā)送 float64 類型的數(shù)據(jù)
<-chan int? ? ? // 只可以用來接收 int 類型的數(shù)據(jù)
另外:
<-總是優(yōu)先和最左邊的類型結(jié)合。以下是幾種組合寫法:
chan<- chan int? ? // 等價 chan<- (chan int)
chan<- <-chan int? // 等價 chan<- (<-chan int)
<-chan <-chan int? // 等價 <-chan (<-chan int)
容量(capacity):
使用make也可以初始化Channel,并且可以設(shè)置channel的容量:
這里的容量可以這樣理解,就是channel可以存儲多少個元素,指定channel的緩沖大小、另外一個nil channel不會通信。
寫法:
ch := make(chan type, value)
當(dāng) value = 0 時,也就說明channel 是無緩沖阻塞讀寫的;
當(dāng) value > 0 時,channel 有緩沖、是非阻塞的,直到寫滿 value 個元素才阻塞寫入。例如:

以上代碼不變,當(dāng)我們把容量設(shè)置為1,就會出現(xiàn)如下問題:

Range和Close
range,這個關(guān)鍵字可以像操作slice或者map一樣,去操作緩存類型的channel;
close,這個關(guān)鍵字主要是用來關(guān)閉channel。關(guān)閉channel之后就無法再發(fā)送任何數(shù)據(jù)。

上面的代碼都是只有一個channel的情況,那么如果存在多個channel的時候,我們該如何操作呢?
Go里面提供了一個關(guān)鍵字select,通過select可以監(jiān)聽channel上的數(shù)據(jù)流動。
它類似switch,但是只是用來處理通訊(communication)操作。它的case可以是send語句(發(fā)送),也可以是receive語句(接收),亦或者default。
default就是當(dāng)監(jiān)聽的channel都沒有準(zhǔn)備好的時候,默認(rèn)執(zhí)行
select默認(rèn)是阻塞的,只有當(dāng)監(jiān)聽的channel中有發(fā)送或接收可以進(jìn)行時才會運(yùn)行,當(dāng)多個channel都準(zhǔn)備好的時候,select是隨機(jī)的選擇一個執(zhí)行的。
代碼如下:
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 3; i++ {
fmt.Println("<-c == ",<-c)
}
quit <- 0
}()
testSelect(c, quit)
}
func testSelect(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
fmt.Println("x == ",x)
fmt.Println("y+x == ",y+x)
case <- quit:
fmt.Println("quit")
return
}
}
}
超時處理:timeout
select有很重要的一個應(yīng)用就是超時處理。如果select中沒有case需要處理,select語句就會一直阻塞著。
這時候我們可能就需要一個超時操作,用來處理超時以避免整個程序進(jìn)入阻塞。Go語言的超時處理是使用的timeout關(guān)鍵字
以下是復(fù)現(xiàn)超時的例子,我分了兩種情況:

下面是第二種,復(fù)現(xiàn)超時的情景:

下面是收集了一些runtime包中關(guān)于處理goroutine的幾個函數(shù):
A: Goexit 這個函數(shù)的意思指:退出當(dāng)前執(zhí)行的goroutine,但是defer函數(shù)還會繼續(xù)調(diào)用
B: Gosched 這個函數(shù)的意思指:讓出當(dāng)前goroutine的執(zhí)行權(quán)限,調(diào)度器安排其他等待的任務(wù)運(yùn)行,并在下次某個時候從該位置恢復(fù)執(zhí)行。
C: NumCPU 這個函數(shù)的意思指:返回 CPU 核數(shù)量
D: NumGoroutine 這個函數(shù)的意思指:返回正在執(zhí)行和排隊(duì)的任務(wù)總數(shù)
E: GOMAXPROCS 這個函數(shù)的意思指:用來設(shè)置可以并行計(jì)算的CPU核數(shù)的最大值,并返回之前的值。
結(jié)語:
關(guān)于Go語言的基礎(chǔ)內(nèi)容,基本上就寫完了。也算是這一階段的學(xué)習(xí)筆記與總結(jié)。
GoLang在學(xué)習(xí)的過程中,個人最深刻的體會就是對內(nèi)存的超嚴(yán)格控制管理、語法簡潔精悍、更小更精準(zhǔn)的協(xié)程執(zhí)行單元、函數(shù)多返回值等等特點(diǎn)。
雖然網(wǎng)上對這么語言褒貶不一,但還是希望這門語言可以發(fā)展的越來越好。
如果這篇文章對您有開發(fā)or學(xué)習(xí)上的些許幫助,希望各位看官留下寶貴的star,謝謝。
Ps:著作權(quán)歸作者所有,轉(zhuǎn)載請注明作者, 商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處(開頭或結(jié)尾請?zhí)砑愚D(zhuǎn)載出處,添加原文url地址),文章請勿濫用,也希望大家尊重筆者的勞動成果