GoLang 高并發 Goroutine(一)
- 并發和并行
- Goroutine
-
- goroutine 是如何工作的
- fork-join并發模型
- 閉包
-
- 例1
- 例2
并發和并行
并發和并行:并發屬于代碼,并行屬于一個運作的程式;具體是我們并沒有編寫并行的代碼,而是期待在運作時能夠并行的并發代碼
Goroutine
goroutine 是一個并發函數(并不是并行)
func main(){
go sayHello()
}
func sayHello(){
fmt.Println("你好")
}
/*
使用匿名函數
func main(){
go func(){
fmt.Println("你好")
}()
}
*/
goroutine 是如何工作的
goroutine 既不是os線程,也不是由語言運作時管理的線程,它是一個協程(協程是一個非搶占式的簡單并發子goroutine(函數、閉包或方法))。 goroutine沒有定義自己的暫停方法和在運作點,Go語言在運作時會觀察goroutine的運作狀态,并在它們阻塞時自動挂起它們,然後在它們不阻塞時恢複它們。
fork-join并發模型
fork指在程式中的任意一點,可以将執行的子分支與其父節點同時運作;join在将來的某個時候,這些并發的執行分支将會合并在一起。
func main(){
sayHello := func(){
fmt.Println("你好")
}
go sayHello()
}
在這個例子中,沒有join點,執行sayHello()的goroutine将在未來某個不确定的時間退出,而程式的其餘部分将會繼續執行;因為不确定sayHello函數是否會執行,具體而言goroutine将會被建立,但是可能在還沒有執行,此時main goroutine已經退出結果是:可能看不到在螢幕上列印出 你好。有一種比較笨且沒有任何實際意義的方法,就是在建立goroutine之後,使用time.Sleep讓main goroutine睡眠一會,這種方式并不能保證結果的正确性,隻是提供了一種讓結果趨近于正确的方式。
是以要建立一個join使得main goroutine 和 sayHello goroutine 同步。
var wg sync.WaitGroup
sayHello := func(){
defer wg.Done()
fmt.Println("你好")
}
wg.Add(1)
go sayHello()
wg.Wait() // 連接配接點,main goroutine等待直到所有wg Done()
閉包
**閉包** 可以從建立它們的作用域擷取變量;思考:如果我們在goroutine上運作一個閉包,那麼閉包是在這些變量的副本上運作,還是原值的引用?
例1
var wg sync.WaitGroup
strs := "你好"
wg.Add(1)
go func(){
defer wg.Done()
strs = "哈哈" //修改了變量strs的值
}()
wg.Wait()
fmt.Println(strs) //哈哈
goroutine在它們所建立的相同位址空間内執行
例2
var wg sync.WaitGroup
for _ , str := range []string{"你好1","你好2","你好3"}{
wg.Add(1)
go func(){
defer wg.Done()
fmt.Println(str)
}()
}
/*
你好3
你好3
你好3
*/
該閉包在使用str的時候,字元串的疊代已經結束了。因為goroutine可能在任何時間點運作,是以不确定在goroutine中會列印什麼值。還有一個問題是,如果在疊代結束後,str的值還在不在記憶體中,goroutine還能不能引用已經超出範圍的内容。這涉及到Go語言運作時會對變量str的值的引用仍然保留,有記憶體轉移到堆,是以goroutine仍然可以繼續通路它。
var wg sync.WaitGroup
for _ , str := range []string{"你好1","你好2","你好3"}{
wg.Add(1)
go func(s string){
defer wg.Done()
fmt.Println(s) //建構一個str的副本
}(str)
}
/*
你好3
你好1
你好2
*/
由于多個goroutine可以在同一個位址空間上運作,是以我們要考慮資源競争(同步問題)。