中文版Concurrency In Go讀書(shū)筆記:https://www.kancloud.cn/mutouzhang/go/596804
1. sync.Cond + time.Tick
cond := sync.NewCond(&sync.Mutex{})
go func() {
for range time.Tick(1 * time.Millisecond) {
cond.Broadcast() // 每隔1ms喚醒阻塞在該條件變臉上的goroutine
}
}()
2. 粗粒度鎖 vs 細(xì)粒度鎖(饑餓現(xiàn)象)
package main
import (
"fmt"
"sync"
"time"
)
/*
* 結(jié)論: 粗粒度鎖(3ns)相比細(xì)粒度鎖(1ns)沥阳,更容易搶占cpu資源损搬,容易導(dǎo)致細(xì)粒度鎖的goroutine餓死
*/
func main() {
var wg sync.WaitGroup
var sharedLock sync.Mutex
const runtime = 1 * time.Second
// 粗粒度鎖goroutine
greedyWorker := func() {
defer wg.Done()
var count int
for begin := time.Now(); time.Since(begin) <= runtime; {
sharedLock.Lock()
time.Sleep(3 * time.Nanosecond)
sharedLock.Unlock()
count++
}
fmt.Printf("Greedy worker was able to execute %v work loops\n", count)
}
// 細(xì)粒度鎖goroutine
politeWorker := func() {
defer wg.Done()
var count int
for begin := time.Now(); time.Since(begin) <= runtime; {
sharedLock.Lock()
time.Sleep(1 * time.Nanosecond)
sharedLock.Unlock()
sharedLock.Lock()
time.Sleep(1 * time.Nanosecond)
sharedLock.Unlock()
sharedLock.Lock()
time.Sleep(1 * time.Nanosecond)
sharedLock.Unlock()
count++
}
fmt.Printf("Polite worker was able to execute %v work loops.\n", count)
}
wg.Add(2)
go greedyWorker()
go politeWorker()
wg.Wait()
}
3. 內(nèi)存同步訪問(wèn):加鎖
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
var memoryAccess sync.Mutex // <1>
var value int
go func() {
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
memoryAccess.Lock() // <2>
value++
memoryAccess.Unlock() // <3>
}()
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
memoryAccess.Lock() // <4>
if value == 0 {
fmt.Printf("the value is %v.\n", value)
} else {
fmt.Printf("the value is %v.\n", value)
}
memoryAccess.Unlock() // <5>
}
4. go執(zhí)行外部命令
exec.Command(命令名,參數(shù)).Run() // 例如 ./cmd -deploy=aaa
5. goroutine背后的知識(shí)
goroutine不是操作系統(tǒng)線程喻杈,也不完全是綠色的線程(由語(yǔ)言運(yùn)行時(shí)管理的線程)局装,其是更高層次的抽象碳想,被成為協(xié)程宋渔。
協(xié)程是非搶占的并發(fā)子程序,也就是說(shuō)goroutine不能被中斷。
Go的獨(dú)特之處在于goroutine與Go的runtime深度整合揪垄,goroutine沒(méi)有定義自己的暫颓钏保或再入點(diǎn),Go的runtime會(huì)監(jiān)視goroutine的運(yùn)行時(shí)行為饥努,并在goroutine阻塞時(shí)自動(dòng)掛起它們捡鱼,在goroutine變通暢時(shí)恢復(fù)它們。
Go的宿主機(jī)制實(shí)現(xiàn)了所謂的M:N調(diào)度器(GPM模型)肪凛,這意味著它可以將M個(gè)綠色線程映射到N個(gè)系統(tǒng)線程堰汉,goroutine隨后被安排在這些綠色線程上辽社。
Go并發(fā)遵循fork-join模型伟墙,即fork的子goroutine在任務(wù)結(jié)束時(shí),最終還是會(huì)合并到主goroutine上的滴铅。go關(guān)鍵字為Go程序?qū)崿F(xiàn)了fork戳葵,fork的執(zhí)行者是goroutine。如下圖所示:
下面的go程序代碼:
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(salutation) // 1
}()
}
wg.Wait()
最終輸出結(jié)果為: good day三次
- main goroutine不能被中斷汉匙,只有在運(yùn)行到wg.Wait時(shí)被阻塞拱烁,此時(shí)其余goroutine才會(huì)被調(diào)度執(zhí)行
- go內(nèi)存管理機(jī)制:salutation變量從棧空間轉(zhuǎn)移至堆空間噩翠,其保存的值為"good day"
因此戏自,程序中的子goroutine在被調(diào)度執(zhí)行時(shí),salutation變量的值均為good day伤锚。
Tips
新建立一個(gè)goroutine有幾千字節(jié)擅笔,這樣的大小幾乎總是夠用的。如果出現(xiàn)不夠用的情況屯援,Go的runtime會(huì)自動(dòng)增加(或縮小)用于存儲(chǔ)堆棧的內(nèi)存猛们,從而允許goroutine存在適量?jī)?nèi)存中。因此狞洋,在C/C++等語(yǔ)言中容易發(fā)生的爆椡涮裕現(xiàn)象在Go中并不會(huì)發(fā)生,因?yàn)間oroutine對(duì)應(yīng)的堆椉茫空間是可以動(dòng)態(tài)增長(zhǎng)的庐橙。在相同的地址空間中創(chuàng)建數(shù)十萬(wàn)個(gè)goroutine是可以的,如果這些goroutine只是執(zhí)行等同于線程的任務(wù)借嗽,那么系統(tǒng)資源的占用將會(huì)更小态鳖。
一種GC無(wú)法回收goroutine的情況:goroutine泄露
go func() {
// goroutine在此處永久阻塞
}()
// do work
一個(gè)計(jì)算goroutine占用內(nèi)存空間大小的程序,通過(guò)運(yùn)行結(jié)果可以看出一個(gè)goroutine是多么的輕量級(jí)淹魄。
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
memConsumed := func() uint64 { // 占用內(nèi)存測(cè)量函數(shù)
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 } // 1 : goroutine將會(huì)一直被阻塞
const numGoroutines = 1e4 // 2 : 創(chuàng)建1W個(gè)goroutine
wg.Add(numGoroutines)
before := memConsumed() // 3 : 測(cè)量創(chuàng)建goroutine前郁惜,內(nèi)存占用大小
for i := numGoroutines; i > 0; i-- {
go noop()
}
wg.Wait()
after := memConsumed() // 4 : 測(cè)量創(chuàng)建1Wgoroutine之后,內(nèi)存占用情況
fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000)
} // 測(cè)量結(jié)果: 每個(gè)goroutine占用內(nèi)存空間大小約為2.61KB
測(cè)試goroutine上下文切換的性能
// 單純模擬兩個(gè)goroutine之間的數(shù)據(jù)傳輸,進(jìn)行g(shù)oroutine上下文切換性能的統(tǒng)計(jì)
func BenchmarkContextSwitch(b *testing.B) {
var wg sync.WaitGroup
begin := make(chan struct{})
c := make(chan struct{})
// 只是單純地模擬兩個(gè)goroutine之間傳送數(shù)據(jù)
var token struct{}
sender := func() {
defer wg.Done()
<-begin //1: 阻塞
for i := 0; i < b.N; i++ {
c <- token //2: 發(fā)送
}
}
receiver := func() {
defer wg.Done()
<-begin //1: 阻塞
for i := 0; i < b.N; i++ {
<-c //3: 接收
}
}
wg.Add(2)
go sender()
go receiver()
b.StartTimer() //4: 啟動(dòng)定時(shí)器
close(begin) //5: 啟動(dòng)兩個(gè)goroutine之間的數(shù)據(jù)傳輸, close channel --> done channel --> 進(jìn)行信號(hào)廣播
wg.Wait()
}
// 基準(zhǔn)測(cè)試結(jié)果如下:
? learndemo **go test -bench=. -cpu=1 /Users/didi/MyWork/PersonalCode/src/go_demo/learndemo/context_switch_test.go**
goos: darwin
goarch: amd64
BenchmarkContextSwitch 10000000 **165 ns/op**
PASS
ok command-line-arguments 1.830s
? learndemo
6. channel相關(guān)知識(shí)
作為擁有channnel的goroutine(生產(chǎn)者)兆蕉,應(yīng)該確保以下三件事情:
- 初始化該channel
- 執(zhí)行寫(xiě)入操作或?qū)⑺袡?quán)交給另一個(gè)goroutine
- 關(guān)閉該channel
作為channel的消費(fèi)者羽戒,只需要考慮兩件事情:
- channel什么時(shí)候被關(guān)閉(close)
- 處理基于任何原因出現(xiàn)的阻塞(block)
一個(gè)簡(jiǎn)單的生產(chǎn)者/消費(fèi)者示例:
chanOwner := func() <-chan int { // 返回一個(gè)只讀channel
resultStream := make(chan int, 5)//1
go func() {//2
defer close(resultStream)//3: defer close channel
for i := 0; i <= 5; i++ {
resultStream <- i // 生產(chǎn)數(shù)據(jù)
}
}()
return resultStream//4
}
// 生產(chǎn)者創(chuàng)建channel,并向channel中寫(xiě)入數(shù)據(jù)虎韵,生產(chǎn)結(jié)束后關(guān)閉channel(defer close)
resultStream := chanOwner()
for result := range resultStream {//5
fmt.Printf("Received: %d\n", result)
} // 消費(fèi)者消費(fèi)數(shù)據(jù)易稠,可能會(huì)阻塞住,且在channel close時(shí)包蓝,執(zhí)行退出操作
fmt.Println("Done receiving!")
7. select
select + time超時(shí)控制
var c <-chan int
select {
case <-c: //1
case <-time.After(1 * time.Second):
fmt.Println("Timed out.")
}
select+default
start := time.Now()
var c1, c2 <-chan int
select {
case <-c1:
case <-c2:
default:
fmt.Printf("In default after %v\n\n", time.Since(start))
}
for-select
done := make(chan interface{})
go func() {
time.Sleep(5 * time.Second)
close(done)
}()
workCounter := 0
loop:
for { // 不斷循環(huán)驶社,判斷select-case條件是否滿足
select {
case <-done:
break loop // break tag使用方法,跳出指定的多層循環(huán)
default:
}
// Simulate work
workCounter++
time.Sleep(1 * time.Second)
}
fmt.Printf("Achieved %v cycles of work before signalled to stop.\n", workCounter)
永久阻塞的select語(yǔ)句
select {} // select沒(méi)有case分支测萎,永久被阻塞
8. GOMAXPROCS
runtime.GOMAXPROCS(runtime.NumCPU()) // 指定G-P-M模型中的P的個(gè)數(shù)亡电,從而決定了其能夠利用的操作系統(tǒng)線程的最大數(shù)目,多核情況下goroutine運(yùn)行的并行程度