網(wǎng)站建設(shè)公司一般多少錢視頻剪輯培訓(xùn)機(jī)構(gòu)
GO語言網(wǎng)絡(luò)編程(并發(fā)編程)Sync
1、Sync
1.1.1. sync.WaitGroup
在代碼中生硬的使用time.Sleep肯定是不合適的,Go語言中可以使用sync.WaitGroup來實(shí)現(xiàn)并發(fā)任務(wù)的同步。 sync.WaitGroup有以下幾個(gè)方法:
方法名 功能
(wg * WaitGroup) Add(delta int) 計(jì)數(shù)器+delta
(wg *WaitGroup) Done() 計(jì)數(shù)器-1
(wg *WaitGroup) Wait() 阻塞直到計(jì)數(shù)器變?yōu)?
sync.WaitGroup內(nèi)部維護(hù)著一個(gè)計(jì)數(shù)器,計(jì)數(shù)器的值可以增加和減少。例如當(dāng)我們啟動(dòng)了N 個(gè)并發(fā)任務(wù)時(shí),就將計(jì)數(shù)器值增加N。每個(gè)任務(wù)完成時(shí)通過調(diào)用Done()方法將計(jì)數(shù)器減1。通過調(diào)用Wait()來等待并發(fā)任務(wù)執(zhí)行完,當(dāng)計(jì)數(shù)器值為0時(shí),表示所有并發(fā)任務(wù)已經(jīng)完成。
我們利用sync.WaitGroup將上面的代碼優(yōu)化一下:
var wg sync.WaitGroupfunc hello() {defer wg.Done()fmt.Println("Hello Goroutine!")
}
func main() {wg.Add(1)go hello() // 啟動(dòng)另外一個(gè)goroutine去執(zhí)行hello函數(shù)fmt.Println("main goroutine done!")wg.Wait()
}
需要注意sync.WaitGroup是一個(gè)結(jié)構(gòu)體,傳遞的時(shí)候要傳遞指針。
1.1.2 sync.Once
說在前面的話:這是一個(gè)進(jìn)階知識點(diǎn)。
在編程的很多場景下我們需要確保某些操作在高并發(fā)的場景下只執(zhí)行一次,例如只加載一次配置文件、只關(guān)閉一次通道等。
Go語言中的sync包中提供了一個(gè)針對只執(zhí)行一次場景的解決方案–sync.Once。
sync.Once只有一個(gè)Do方法,其簽名如下:
func (o *Once) Do(f func()) {}
注意:如果要執(zhí)行的函數(shù)f需要傳遞參數(shù)就需要搭配閉包來使用。
加載配置文件示例
延遲一個(gè)開銷很大的初始化操作到真正用到它的時(shí)候再執(zhí)行是一個(gè)很好的實(shí)踐。因?yàn)轭A(yù)先初始化一個(gè)變量(比如在init函數(shù)中完成初始化)會增加程序的啟動(dòng)耗時(shí),而且有可能實(shí)際執(zhí)行過程中這個(gè)變量沒有用上,那么這個(gè)初始化操作就不是必須要做的。我們來看一個(gè)例子:
var icons map[string]image.Imagefunc loadIcons() {icons = map[string]image.Image{"left": loadIcon("left.png"),"up": loadIcon("up.png"),"right": loadIcon("right.png"),"down": loadIcon("down.png"),}
}// Icon 被多個(gè)goroutine調(diào)用時(shí)不是并發(fā)安全的
func Icon(name string) image.Image {if icons == nil {loadIcons()}return icons[name]
}
多個(gè)goroutine并發(fā)調(diào)用Icon函數(shù)時(shí)不是并發(fā)安全的,現(xiàn)代的編譯器和CPU可能會在保證每個(gè)goroutine都滿足串行一致的基礎(chǔ)上自由地重排訪問內(nèi)存的順序。loadIcons函數(shù)可能會被重排為以下結(jié)果:
func loadIcons() {icons = make(map[string]image.Image)icons["left"] = loadIcon("left.png")icons["up"] = loadIcon("up.png")icons["right"] = loadIcon("right.png")icons["down"] = loadIcon("down.png")
}
在這種情況下就會出現(xiàn)即使判斷了icons不是nil也不意味著變量初始化完成了??紤]到這種情況,我們能想到的辦法就是添加互斥鎖,保證初始化icons的時(shí)候不會被其他的goroutine操作,但是這樣做又會引發(fā)性能問題。
使用sync.Once改造的示例代碼如下:
var icons map[string]image.Imagevar loadIconsOnce sync.Oncefunc loadIcons() {icons = map[string]image.Image{"left": loadIcon("left.png"),"up": loadIcon("up.png"),"right": loadIcon("right.png"),"down": loadIcon("down.png"),}
}// Icon 是并發(fā)安全的
func Icon(name string) image.Image {loadIconsOnce.Do(loadIcons)return icons[name]
}
sync.Once其實(shí)內(nèi)部包含一個(gè)互斥鎖和一個(gè)布爾值,互斥鎖保證布爾值和數(shù)據(jù)的安全,而布爾值用來記錄初始化是否完成。這樣設(shè)計(jì)就能保證初始化操作的時(shí)候是并發(fā)安全的并且初始化操作也不會被執(zhí)行多次。
1.1.3 sync.Map
Go語言中內(nèi)置的map不是并發(fā)安全的。請看下面的示例:
var m = make(map[string]int)func get(key string) int {return m[key]
}func set(key string, value int) {m[key] = value
}func main() {wg := sync.WaitGroup{}for i := 0; i < 20; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)set(key, n)fmt.Printf("k=:%v,v:=%v\n", key, get(key))wg.Done()}(i)}wg.Wait()
}
上面的代碼開啟少量幾個(gè)goroutine的時(shí)候可能沒什么問題,當(dāng)并發(fā)多了之后執(zhí)行上面的代碼就會報(bào)fatal error: concurrent map writes錯(cuò)誤。
像這種場景下就需要為map加鎖來保證并發(fā)的安全性了,Go語言的sync包中提供了一個(gè)開箱即用的并發(fā)安全版map–sync.Map。開箱即用表示不用像內(nèi)置的map一樣使用make函數(shù)初始化就能直接使用。同時(shí)sync.Map內(nèi)置了諸如Store、Load、LoadOrStore、Delete、Range等操作方法。
var m = sync.Map{}func main() {wg := sync.WaitGroup{}for i := 0; i < 20; i++ {wg.Add(1)go func(n int) {key := strconv.Itoa(n)m.Store(key, n)value, _ := m.Load(key)fmt.Printf("k=:%v,v:=%v\n", key, value)wg.Done()}(i)}wg.Wait()
}