前幾天被小伙伴發(fā)現(xiàn)半年前寫(xiě)的代碼里面有一個(gè)并發(fā)的bug『菰梗回想一下,golang并發(fā)這邊的知識(shí)確實(shí)忘得差不多了邑遏,打算寫(xiě)兩篇筆記記錄一下佣赖。golang里的并發(fā)主要有兩種,一種是使用goroutine(類似于線程)并通過(guò)channel實(shí)現(xiàn)線程間通信记盒;另一種是通過(guò)shared variables來(lái)實(shí)現(xiàn)憎蛤。我們先來(lái)看看第一種
goroutine
首先我們來(lái)看一個(gè)goroutine的例子
func main (){
go func() {
for i := 0; i < 10; i++ {
fmt.Println("foo", i)
}
}()
fmt.Println("bar")
}
上面的go foo()開(kāi)了一個(gè)新的goroutine,但是它并不會(huì)跑完(可能能跑一兩個(gè)循環(huán)),因?yàn)閙ain先退出了纪吮,整個(gè)程序也就結(jié)束了俩檬。為了讓它能夠跑完栏豺,我們可以讓main等一等它。
var wg sync.WaitGroup
func main (){
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Println("foo", i)
}
}()
fmt.Println("bar")
wg.Wait()
}
為了顯示他們是并發(fā)的豆胸,我們多開(kāi)幾個(gè)goroutine,看他們的輸出是否是亂序的奥洼。
var wg sync.WaitGroup
func foo(s string){
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Println(s, i)
}
}
func main (){
wg.Add(3)
go foo("foo")
go foo("oof")
go foo("bar")
wg.Wait()
}
有興趣的同學(xué)可以看看net/http是如何使用goroutine的
Channels
"If goroutines are the activities of a concurrent go program, channels are the connections between them"
這句話是對(duì)channel最好的解釋了。channel用于在goroutine之間傳遞值晚胡,是一種"線程"間通信方式灵奖。
ch := make(chan int) //創(chuàng)建一個(gè)unbuffered channel
ch := make(chan int, 3) //創(chuàng)建一個(gè)buffered channel,容量為3
ch <- x //發(fā)送值到channel
x = <- ch //從channel中取出值
<- ch //取值并丟棄
close(ch) //關(guān)閉channel
語(yǔ)法太簡(jiǎn)單了!這年頭連編程語(yǔ)言都要擬物化了估盘!
unbuffered channel
發(fā)送數(shù)據(jù)到unbuffered channel的操作會(huì)阻塞發(fā)送的goroutine,直到有某個(gè)線程接收了這個(gè)channel的值瓷患。而如果一個(gè)goroutine試圖去一個(gè)沒(méi)有值的channel上取值,它也會(huì)被阻塞遣妥,直到這個(gè)channel上被發(fā)送了值擅编。
func main() {
c := make(chan int)
go func(c chan int) {
time.Sleep(3 * time.Second)
fmt.Println("before received")
fmt.Println(<- c)
}(c)
c <- 1
fmt.Println("after received")
}
運(yùn)行上面代碼我們明顯看到,在channel中的值被讀取之前箫踩,main被阻塞了爱态,直到channel被讀取,main才繼續(xù)運(yùn)行境钟。
func main() {
c := make(chan int)
go func(c chan int) {
fmt.Println(<- c)
fmt.Println("after received")
}(c)
time.Sleep(3 * time.Second)
fmt.Println("before sending")
c <- 1
time.Sleep(1 * time.Second)
}
上面的代碼中锦担,新開(kāi)的goroutine首先去讀channel,可是由于channel中沒(méi)有值,所以它被阻塞了慨削,直到main中向channel發(fā)送值洞渔,goroutine才拿到它想要的值并繼續(xù)運(yùn)行。
close channel
發(fā)送完成后缚态,可以關(guān)閉channel磁椒,關(guān)閉后所有對(duì)這個(gè)channel的寫(xiě)操作都會(huì)panic,而讀操作依舊可以進(jìn)行,當(dāng)所有值都讀完后玫芦,繼續(xù)讀該channel會(huì)得到zero value
func main() {
naturals := make(chan int)
squares := make(chan int)
go func() {
for x := 0; x < 100 ;x++ {
naturals <- x
}
close(naturals)
}()
go func() {
for x := range naturals{
squares <- x * x
}
close(squares)
}()
for x := range squares{
fmt.Println(x)
}
}
buffered channels
buffered channels是一個(gè)有限長(zhǎng)隊(duì)列浆熔,下面我們來(lái)創(chuàng)建一個(gè)容量為3的buffered channels
bc := make(chan int, 3)
如果bc里面已經(jīng)有了三個(gè)沒(méi)有被讀取的值,繼續(xù)往里面發(fā)送的話程序就會(huì)出錯(cuò)姨俩。我們可以用cap函數(shù)和len函數(shù)來(lái)檢查bc的容量以及里面現(xiàn)在有幾個(gè)值蘸拔。