Goroutine
什么是 Goroutine
Goroutine 是 Go 并行設(shè)計(jì)的核心。Goroutine 說(shuō)到底其實(shí)就是協(xié)程获三,它比線程更小食绿,十幾個(gè) Goroutine 可能體現(xiàn)在地城就是五六個(gè)線程,Go 語(yǔ)言內(nèi)部實(shí)現(xiàn)了這些 Goroutine 之間的內(nèi)存共享磕潮。
執(zhí)行 Goroutine 只需極少的棧內(nèi)存(大概是 4~5KB)翠胰,會(huì)根據(jù)相應(yīng)的數(shù)據(jù)伸縮。也正因?yàn)槿绱俗愿赏瑫r(shí)運(yùn)行成千上萬(wàn)個(gè)并發(fā)任務(wù)之景。Goroutine 比 thread 更易用、更高效膏潮、更輕便锻狗。
Goroutine 的創(chuàng)建
只需要在函數(shù)調(diào)用語(yǔ)句前添加 go
關(guān)鍵字,就可創(chuàng)建并發(fā)執(zhí)行單元焕参。開(kāi)發(fā)人員無(wú)需了解任何執(zhí)行細(xì)節(jié)轻纪,調(diào)度器會(huì)自動(dòng)將其安排到合適的系統(tǒng)線程上執(zhí)行。
Goroutine 的特征
主 go 程結(jié)束叠纷,子 go 程隨之退出刻帚。
示例
package main
import (
"fmt"
"time"
)
func sing() {
for i := 0; i < 50; i++ {
fmt.Println("----我正在唱歌----")
time.Sleep(100 * time.Millisecond)
}
}
func dance() {
for i := 0; i < 50; i++ {
fmt.Println("----我正在跳舞----")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// 子 go 程
go sing()
go dance()
// 主 go程循環(huán)打印
for {
;
}
}
程序運(yùn)行結(jié)果:
runtime 包
-
Gosched
runtime.Gosched()
用于讓出 CPU 時(shí)間片,讓出當(dāng)前 goroutine 的執(zhí)行權(quán)限涩嚣,調(diào)度器安排其他等待的任務(wù)運(yùn)行崇众,并在下次再獲得 CPU 時(shí)間輪片的時(shí)候,從該處讓 CPU 的位置恢復(fù)執(zhí)行航厚。 ---時(shí)間片輪傳調(diào)度算法 -
Goexit
調(diào)用
runtime.Goexit()
將立即終止當(dāng)前 goroutine 執(zhí)行顷歌,調(diào)度器確保所有已注冊(cè) defer 延遲調(diào)用被執(zhí)行。- return:返回當(dāng)前函數(shù)調(diào)用到調(diào)用者那里去幔睬。return 之前的 defer 注冊(cè)生效眯漩。
- Goexit():結(jié)束調(diào)用該函數(shù)的當(dāng)前 go 程。Goexit() 之前注冊(cè)的 defer 都生效溪窒。
-
GOAMAXPROCS
調(diào)用
runtime.GOMAXPROCS()
坤塞,用來(lái)設(shè)置可以并行計(jì)算的 CPU 核數(shù)的最大值冯勉,返回返回之前的值。首次調(diào)用返回默認(rèn)值摹芙。 -
補(bǔ)充
每當(dāng)有一個(gè)進(jìn)程啟動(dòng)時(shí)灼狰,系統(tǒng)會(huì)自動(dòng)打開(kāi)三個(gè)文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出浮禾、標(biāo)準(zhǔn)錯(cuò)誤交胚。 ---對(duì)應(yīng)三個(gè)文件:stdin、stdout盈电、stderr肉瓦。
當(dāng)進(jìn)程運(yùn)行結(jié)束等缀,操作系統(tǒng)自動(dòng)關(guān)閉三個(gè)文件霹抛。
channel
channel 是 Go 語(yǔ)言中的一個(gè) 核心類(lèi)型栅表,可以把它看成管道(通道 FIFO)。并發(fā)核心單元通過(guò)它就可以發(fā)送或者接收數(shù)據(jù)進(jìn)行通訊吸重。
channel 是一個(gè)數(shù)據(jù)類(lèi)型互拾,只要用來(lái)解決協(xié)程的同步問(wèn)題以及協(xié)程之間數(shù)據(jù)共享(數(shù)據(jù)傳遞)的問(wèn)題。
goroutine 運(yùn)行在相同的地址空間嚎幸,因此訪問(wèn)共享內(nèi)存必須做好同步颜矿。goroutine 奉行通過(guò)通信來(lái)共享內(nèi)存,而不是共享內(nèi)存來(lái)通信
引用類(lèi)型 channel 可用于多個(gè) goroutine 通訊嫉晶。其內(nèi)部實(shí)現(xiàn)了同步骑疆,確保并發(fā)安全。
定義 channel 變量
chan
是創(chuàng)建 channel 所需使用的關(guān)鍵字替废。Type
代表指定 channel 手法數(shù)據(jù)的類(lèi)型箍铭。
make(chan Type) // 等價(jià)于 make(chan Type, 0)
make(chan Type, capacity)
和 map 類(lèi)似,channel 也是一個(gè)對(duì)應(yīng) make 創(chuàng)建的底層數(shù)據(jù)結(jié)構(gòu)的引用舶担。
和其它的引用類(lèi)型一樣坡疼,channel 的零值也是 nil。
賦值 channel 或者用于函數(shù)參數(shù)傳遞衣陶,都是使用的 channel 的引用。
當(dāng)闡述 capacity = 0
時(shí)闸氮,channel 是無(wú)緩沖阻塞讀寫(xiě)的剪况;當(dāng) capacity > 0
時(shí),channel 有緩沖蒲跨、是非阻塞的译断,直到寫(xiě)滿 capacity 個(gè)元素才阻塞寫(xiě)入。
channel 非常像生活中的管道或悲,一邊可以存放東西孙咪,另一邊可以取出東西堪唐。channel 通過(guò)操作符 <-
來(lái)接收和發(fā)送數(shù)據(jù):
channel := make(chan int)
channel <- value // 寫(xiě)端(傳入端):發(fā)送 value 到 channel
x := <- channel // 讀端(傳出端):從 channel 中接收數(shù)據(jù),并賦值給 x
x, ok := <- channel // 功能同上翎蹈,同時(shí)檢查通道是否已關(guān)閉或者是否為空
默認(rèn)情況下淮菠,channel 接收和發(fā)送數(shù)據(jù)都是阻塞的,除非另一端已經(jīng)準(zhǔn)備好荤堪,這樣就使得 goroutine 同步變得更加簡(jiǎn)單合陵,而不需要顯示 lock。
無(wú)緩沖 channel
make(chan Type) // 等價(jià)于 make(chan Type, 0)
無(wú)緩沖的通道(unbuffered channel):是指在接收前沒(méi)有能力保存任何值的通道澄阳。
如果沒(méi)有指定緩沖區(qū)容量拥知,那么該通道就是同步的,因此會(huì)阻塞到發(fā)送者準(zhǔn)備好發(fā)送和接收者準(zhǔn)備好接收碎赢,才解除阻塞低剔。
有緩沖 channel
make(chan Type, capacity) // capacity > 0
有緩沖的通道(buffered channel)是一種在被接收前能存儲(chǔ)一個(gè)或者多個(gè)數(shù)據(jù)值的通道。
- 這種類(lèi)型的通道并不強(qiáng)制要求 goroutine 之間必須同時(shí)完成發(fā)送和接收肮塞。通道會(huì)阻塞發(fā)送和接收動(dòng)作的條件也不同户侥。
- 只要通道中沒(méi)有要接收的值時(shí),接收動(dòng)作才會(huì)阻塞峦嗤。
- 只要通道沒(méi)有可用緩沖區(qū)容納被發(fā)送的值時(shí)蕊唐,發(fā)送動(dòng)作才會(huì)阻塞。
如果給定一個(gè)緩沖區(qū)容量烁设,通道就是異步的替梨。只要緩沖區(qū)有未使用空間用于發(fā)送數(shù)據(jù),或還包含可以接收的數(shù)據(jù)装黑,那么其通信就會(huì)無(wú)阻塞的進(jìn)行副瀑。
有緩沖和無(wú)緩沖 channel 的區(qū)別
- 無(wú)緩沖的通道保證進(jìn)行發(fā)送和接收的 goroutine 會(huì)在同一時(shí)間進(jìn)行數(shù)據(jù)交換;有緩沖的通道沒(méi)有這種保證恋谭。
關(guān)閉 channel
如果發(fā)送者知道糠睡,沒(méi)有更多的值要發(fā)送到 channel 的話,那么有必要讓接收者也能及時(shí)知道沒(méi)有多余的值可接受疚颊,因此接收者可以停止不必要的接收等待狈孔。可以通過(guò)內(nèi)置的 close()
函數(shù)來(lái)關(guān)閉 channel材义。
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//把 close(c) 注釋掉均抽,程序會(huì)一直阻塞在 if data, ok := <-c; ok 那一行
close(c)
}()
for {
//ok為true說(shuō)明channel沒(méi)有關(guān)閉,為false說(shuō)明管道已經(jīng)關(guān)閉
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
// 可以使用 range 來(lái)迭代不斷操作 channel
// for data := range c {
// fmt.Println(data)
// }
fmt.Println("Finished")
}
注意
- channel 不像文件一樣需要經(jīng)常去關(guān)閉其掂,只有當(dāng)你確實(shí)沒(méi)有任何發(fā)送數(shù)據(jù)了油挥,或者你想顯示的結(jié)束 range 循環(huán)之類(lèi)的,才去關(guān)閉 channel
- 關(guān)閉 channel 后,無(wú)法向 channel 再發(fā)送數(shù)據(jù)(引發(fā) panic 錯(cuò)誤后導(dǎo)致接收立即返回零值)
- 關(guān)閉 channel 后深寥,可以繼續(xù)從 channel 接收數(shù)據(jù)
- 對(duì)于 nil channel攘乒,無(wú)論收發(fā)都會(huì)被阻塞。
單向 channel 及應(yīng)用
默認(rèn)情況下惋鹅,通道 channel 是雙向的则酝。也就是,既可以往里面發(fā)送數(shù)據(jù)负饲,也可以從里面接收數(shù)據(jù)堤魁。
但是,我們經(jīng)常見(jiàn)一個(gè)通道作為參數(shù)進(jìn)行傳遞而只希望對(duì)方是單向使用的返十,要么只讓它發(fā)送數(shù)據(jù)妥泉,要么只讓它接收數(shù)據(jù),這時(shí)候我們可以指定通道的方向
var ch1 chan int // ch1 是一個(gè)正常的 channel洞坑,是雙向的
var ch2 chan<- float64 // ch2 是單向的 channel盲链,只用于寫(xiě) float64 數(shù)據(jù)
var ch3 <-chan int // ch3 是單向 channel,只用于讀 int 數(shù)據(jù)
-
chan <-
:表示數(shù)據(jù)進(jìn)入管道迟杂,要把數(shù)據(jù)寫(xiě)進(jìn)管道刽沾,對(duì)于調(diào)用者就是輸出。 -
<- chan
:表示數(shù)據(jù)從管道出來(lái)排拷,對(duì)于調(diào)用者就是得到管道的數(shù)據(jù)侧漓,就是輸入。
可以將 channel 隱式轉(zhuǎn)換為單向隊(duì)列监氢,只收或者只發(fā)布蔗,不能將單向 channel 轉(zhuǎn)換為普通 channel:
ch := make(chan int, 3)
var send chan<- int = ch // send-only
var recv <-chan int = ch // receive-only
send <- 1
//<-send //invalid operation: <-send (receive from send-only type chan<- int)
<-recv
//recv <- 2 //invalid operation: recv <- 2 (send to receive-only type <-chan int)
//不能將單向 channel 轉(zhuǎn)換為普通 channel
d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int