寫在前面
併發 Concurrency
- go的併發是經過goroutine來實現的
- 它的使用,就是在某個操做前加上go關鍵字
go f(x, y, z)
- 什麼叫當前的goroutine?
package main
import "fmt"
func main() {
fmt.println("hello,world!")
}
- 若是程序開始執行,那麼上面的的就是當前的goroutine了
- 什麼是非當前的goroutine
import "fmt"
func NewGoroutine() {
fmt.Println("new goroutine!")
}
func main() {
go NewGoroutine() //這裏就添加了新的goroutine了(運行起來纔算)
fmt.println("hello,world!")
}
- 可是,運行起來並無看到打印出"new goroutine"
- 這是由於當前的goroutine並無義務等待這個新的goroutine執行,它本身執行完就結束了
- 那麼應該如何改變呢?Go提供了一些列管理goroutine的方法,其中最主要就是channel和Mutex了
Channel
- Channel也是一種類型,它的定義和使用比較特別
ch := make(chan int)
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.
- 定義通常採用make函數進行,chan關鍵字是固定的,後面該channel能夠放置的類型,例子中的就是int
- 它的基本使用有兩種模式:
- 其餘變量往channel傳送數據:ch<-v
- 其餘變量從channel接受數據:v := <-ch
- 兩種操做是互相等待的,以例子中的說明
- 當A處的數據創送給ch時(ch <- A),它會先判斷,是否其它地方須要從ch中獲取數據
- 如果:則數據從A處傳到指定的地方
- 若不是:A處的goroutine將被阻塞(其實也就是停下來),等到其它任意地方須要從ch中獲取數據時,A處的goroutine纔開始運行
- 而B處須要從ch獲取數據(B := <-ch),它會先判斷,是否某個地方給ch傳送數據
- 如果:則數據從某個地方傳送到B處
- 若不是:B處的goroutine將被阻塞(其實也就是停下來),等到其它任意地方傳送數據給ch,B處的goroutine纔開始運行
- 簡單的比喻就是傳送門兩側,須要等待兩邊的人都贊成才能打開
- 或者這樣理解,若是把這個過程比喻成接力賽
- 接力棒是數據,每一個隊有多個成員
- A成員的目標就是將接力棒交給B成員,但須要奔跑,須要時間,B成員須要等待A成員(被阻塞)
- 下面是例子代碼:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
- 讀者們能夠試着把ch定義成普通的channel:ch := make(chan int)
- 這樣會報錯,緣由在於:
- 首先這是在一個goroutine中
- 其次,ch <-1執行時,會一直等待其它goroutine執行從ch中拿數據的操做
關鍵字:range,close
- 當某個goroutine中的某個操做須要不停從channel c中獲取數據時,能夠用到range關鍵字
- 而對於上述操做,並不須要擔憂當channel c再也不有數據的事情,這個事情是由運行發送數據到channel c的goroutine關心的
- 若是發送數據給channel c的goroutine不須要再發送數據了,那就須要執行close(c)的操做,不然程序會報錯
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
- 對於上述代碼,先關注main函數
- go fibonacci(cap(c), c)這一句新建了一個goroutine,其中cap(c)是用來計算channel c的容量
- for i := range c這句,
- 將不停循環判斷,
- 一有數據傳送到channel c,i 就能獲取該數據,並執行循環語句中的內容
- 當channel c被關閉,循環判斷將結束
- 再看fibonacci函數
- fibonacci數列的計算,其中for循環裏的內容c <- x這句,就是向channel c傳送數據,以後main中的for語句會執行相應操做
- 最後close(c)這句,關閉channel c,以後main的for語句結束執行
關鍵字 select
- select很特別,它通常會搭配for循環使用,行爲上和switch語句相似
- select語句判斷是是否執行了某些操做,若是是,我就將執行XXX操做
- switch語句判斷的一般是某某值是否等於某某值這種true or false的問題
- 代碼:
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
- main()中,建立了channel c和channel quit
- 一個匿名函數做爲新建goroutine的運行內容
- 而後運行fibonacci函數(這是當前goroutine中的)
- 在fibonacci函數中,使用for進行無限循環不停執行select語句
- 在這裏select語句會不停判斷每一個case語句中的操做
- 像case c <- x,判斷條件也是一種操做,因此會先把x傳送給c,而後阻塞,等待其餘向channel c要數據的操做
- 這時,匿名函數(新建的goroutine)的for循環中fmt.Println(<-c)將會執行,執行完成後,又會進行等待其餘操做向channel c 傳遞數據
- 而在fibonacci中,case c <- x能順利進行後,將執行x, y = y, x+y,而後進入新的循環中執行select語句
- 過程就像上述那樣,進行循環,判斷,阻塞,解除阻塞。。。
- 等到goroutine的循環結束,並執行quit <- 0後,就會執行select語句中的case <-quit的內容了,以後程序返回,當前goroutine結束
- 固然,select也會搭配關鍵字default:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
- 當其它case中的channel都每準備好,就會執行default中的語句
sync.Mutex
- 解決併發衝突的辦法還有比較經典的Mutex
- Mutex能夠進行加鎖Lock()和解鎖操做Unlock()
- 這裏不展開了,留在下一篇,也是a tour of go的最後一道習題,我的認爲這個題目出得很是好,值得詳細講解