大家好,我是棧長。 今天給大家通報一則框架更新消息,時隔兩個月,Spring Cloud 2021.0.5 最新版發佈了,來看下最新的 Spring Cloud 版本情況: Spring Cloud 無疑是現在 Java 微服務事實上的標準,完全基於 Spring Boot 構建,依賴 Spring ...
今天這篇筆記重點講goroutine
首先怎麼定義goroutine
很簡單,在方法前面加上go就可以了
func main() {
go sayHello()
}
func sayHello() {
fmt.Println("hello")
}
也可以直接這樣寫, 基於匿名函數
go func() {
fmt.Println("hello")
}()
go 語言至少有一個main goroutine, 另外調用的sayhello goroutine和main goroutine併發執行,會在main goroutine退出後退出,所以我們上面的代碼是有問題的,它不會列印出"hello". 因為main goroutine退出了,它沒有機會執行。我們需要用到WaitGroup在main goroutine上面等待它,如下代碼
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello")
}()
wg.Wait()
wg.Add(1), 表示會執行一次, wg.Done就是說執行了一次。 wg.Wait()會等待add的一次done.
main goroutine 可以和 goroutine共用相同的地址空間執行,如下代碼
var wg sync.WaitGroup
salutation := "hello"
wg.Add(1)
go func() {
defer wg.Done()
salutation = "welcome"
}()
wg.Wait()
fmt.Println(salutation)
會列印出“welcome”, salutation變數在goroutine中被改變後,也會在main goroutine中生效。
我們來看下麵的代碼會輸出什麼
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
wg.Add(1)
go func() {
defer wg.Done()
mt.Println(salutation)
}()
}
wg.Wait()
它會列印三個good day, 為什麼呢? 作者的解釋是在goroutine開始之前迴圈有很高的概率會退出,salutation的值不在範圍之內, go 語言運行時會足夠小心的將變數salutation值得引用仍然保留,由記憶體轉移到堆。 我不是特別明白作者得解釋,我自己理解下,感覺是for迴圈是main goroutine, 它執行for很快,調用goroutine得時候它已經迴圈完了,所以就拿到最後得值了。
這個我們想列印三個不同得值,使用下麵得代碼
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
wg.Add(1)
go func(value string) {
defer wg.Done()
fmt.Println(value)
}(salutation)
}
wg.Wait()
這樣三個goroutine使用的是各自的副本。
goroutine的開銷是什麼樣的呢?
一個goroutine占多少記憶體? 作者書上說大概是2.817KB, 我自己實驗了下我的機器上是9.072KB,而OS線程的一般會是2M, 差距還是有些大,所以我們說啟動百萬的goroutine也是很正常的,而線程一般幾十個就不錯了。 下麵是示例代碼
func main() {
memConsumed := func() uint64 {
runtime.GC()
var s runtime.MemStats
runtime.ReadMemStats(&s)
return s.Sys
}
var c <-chan interface{}
var wg sync.WaitGroup
noop := func() {
wg.Done()
<-c
}
const numGoroutines = 5e4
wg.Add(numGoroutines)
before := memConsumed()
for i := numGoroutines; i > 0; i-- {
go noop()
}
after := memConsumed()
fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000)
}
numGoroutines是我們想創建的goroutine數量
noop 這個方法一直等channel裡面的value,它不會退出,創建了的goroutine一直在記憶體中。
memConsumed 方法統計當前的記憶體, 我們在開始的時候統計下,在結束的時候統計下,相減後就得到消耗的記憶體。
作者還列舉了goroutine的上下文切換耗時225ns, 而線程切換1467ns, 相差也比較大,
最後
作者得出的結論是使用goroutine非常廉價