淺析 go sync包
背景介紹
盡管 Golang 推薦通過(guò) channel 進(jìn)行通信和同步枢贿,但在實(shí)際開(kāi)發(fā)中 sync 包用得也非常的多
var a = 0
// 啟動(dòng) 100 個(gè)協(xié)程,需要足夠大
// var lock sync.Mutex
for i := 0; i < 100; i++ {
go func(idx int) {
// lock.Lock()
// defer lock.Unlock()
a += 1
fmt.Printf("goroutine %d, a=%d\n", idx, a)
}(i)
}
// 等待 1s 結(jié)束主程序
// 確保所有協(xié)程執(zhí)行完
time.Sleep(time.Second)
互斥鎖sync.Mutex吊趾,讀寫(xiě)鎖sync.RWMutex
鎖的一些概念及使用方法,
整個(gè)包圍繞 Locker 進(jìn)行,這是一個(gè) interface:
type Locker interface {
Lock()
Unlock()
}
互斥鎖 Mutex
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
使用須知:
一個(gè)互斥鎖只能同時(shí)被一個(gè) goroutine 鎖定,其它 goroutine 將阻塞直到互斥鎖被解鎖(重新?tīng)?zhēng)搶對(duì)互斥鎖的鎖定)
對(duì)一個(gè)未鎖定的互斥鎖解鎖將會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤烁涌。
讀寫(xiě)鎖 RWMutex
func (rw *RWMutex) Lock() //寫(xiě)鎖定
func (rw *RWMutex) Unlock() //寫(xiě)解鎖
func (rw *RWMutex) RLock() //讀鎖定
func (rw *RWMutex) RUnlock() //讀解鎖
使用須知:
- 當(dāng)有一個(gè) goroutine 獲得寫(xiě)鎖定,其它無(wú)論是讀鎖定還是寫(xiě)鎖定都將阻塞直到寫(xiě)解鎖酒觅;
- 當(dāng)有一個(gè) goroutine 獲得讀鎖定撮执,其它讀鎖定任然可以繼續(xù);
- 當(dāng)有一個(gè)或任意多個(gè)讀鎖定舷丹,寫(xiě)鎖定將等待所有讀鎖定解鎖之后才能夠進(jìn)行寫(xiě)鎖定抒钱。所以說(shuō)這里的讀鎖定(RLock)目的其實(shí)是告訴寫(xiě)鎖定:有很多人正在讀取數(shù)據(jù),你給我站一邊去,等它們讀(讀解鎖)完你再來(lái)寫(xiě)(寫(xiě)鎖定)谋币。
var count int
var rw sync.RWMutex
func main() {
ch := make(chan struct{}, 10)
for i := 0; i < 5; i++ {
go read(i, ch)
}
for i := 0; i < 5; i++ {
go write(i, ch)
}
for i := 0; i < 10; i++ {
<-ch
}
}
func read(n int, ch chan struct{}) {
rw.RLock()
fmt.Printf("goroutine %d 進(jìn)入讀操作...\n", n)
v := count
fmt.Printf("goroutine %d 讀取結(jié)束仗扬,值為:%d\n", n, v)
rw.RUnlock()
ch <- struct{}{}
}
func write(n int, ch chan struct{}) {
rw.Lock()
fmt.Printf("goroutine %d 進(jìn)入寫(xiě)操作...\n", n)
v := rand.Intn(1000)
count = v
fmt.Printf("goroutine %d 寫(xiě)入結(jié)束,新值為:%d\n", n, v)
rw.Unlock()
ch <- struct{}{}
}
sync.Waitgroup,sync.Once
WaitGroup
用于等待一組 goroutine 結(jié)束蕾额,用法很簡(jiǎn)單早芭。它有三個(gè)方法:
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
說(shuō)明: Add 用來(lái)添加 goroutine 的個(gè)數(shù)。Done 執(zhí)行一次數(shù)量減 1诅蝶。Wait 用來(lái)等待結(jié)束.
注意: wg.Add() 方法一定要在 goroutine 開(kāi)始前執(zhí)行哦退个。
var wg sync.WaitGroup
for i, s := range seconds {
// 計(jì)數(shù)加 1
wg.Add(1)
go func(i, s int) {
// 計(jì)數(shù)減 1
defer wg.Done()
fmt.Printf("goroutine%d 結(jié)束\n", i)
}(i, s)
}
// 等待執(zhí)行結(jié)束
wg.Wait()
fmt.Println("所有 goroutine 執(zhí)行結(jié)束")
Once
func (o *Once) Do(f func())
使用 sync.Once 對(duì)象可以使得函數(shù)多次調(diào)用只執(zhí)行一次
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
----
# 打印結(jié)果
Only once
sync.Map
sync.Map是一個(gè)并發(fā)版本的Go語(yǔ)言的map
- 使用Store(interface {},interface {})添加元素调炬。
- 使用Load(interface {}) interface {}檢索元素语盈。
- 使用Delete(interface {})刪除元素。
- 使用LoadOrStore(interface {}缰泡,interface {}) (interface {}刀荒,bool)檢索或添加之前不存在的元素。如果鍵之前在map中存在棘钞,則返回的布爾值為true缠借。
- 使用Range遍歷元素。
var m sync.Map
// m:=&sync.Map{}
// 添加元素
m.Store(1, "one")
m.Store(2, "two")
// 迭代所有元素
m.Range(func(key, value interface{}) bool {
fmt.Printf("%d: %s\n", key.(int), value.(string))
return true
})
// 獲取元素1
value, ok := m.Load(1)
fmt.Println(value,ok) //one true
// 返回已存value武翎,否則把指定的鍵值存儲(chǔ)到map中
value, loaded := m.LoadOrStore(1, "three")
fmt.Println(value,loaded) //one true
value1, loaded1 := m.LoadOrStore(3, "three")
fmt.Println(value1,loaded1) //three false
m.Delete(3)
sync.Pool
在 golang 中有一個(gè)池pool烈炭,目的:
復(fù)用已經(jīng)使用過(guò)的對(duì)象,來(lái)達(dá)到優(yōu)化內(nèi)存使用和回收的目的宝恶。
說(shuō)白了符隙,一開(kāi)始這個(gè)池子會(huì)初始化一些對(duì)象供你使用,如果不夠了呢垫毙,自己會(huì)通過(guò)new產(chǎn)生一些霹疫,當(dāng)你放回去了之后這些對(duì)象會(huì)被別人進(jìn)行復(fù)用,當(dāng)對(duì)象特別大并且使用非常頻繁的時(shí)候可以大大的減少對(duì)象的創(chuàng)建和回收的時(shí)間综芥。
簡(jiǎn)單案例
一共只有三個(gè)方法我們需要知道的:New丽蝎、Put、Get
var pool = sync.Pool{
New: func() interface{} {
return "123"
},
}
func main() {
t := pool.Get().(string)
fmt.Println(t)
pool.Put("321")
t2 := pool.Get().(string)
fmt.Println(t2)
}
---輸出:
123
321
源碼結(jié)構(gòu)分析
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
localSize uintptr // size of the local array
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims array
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() interface{}
}
// Local per-P Pool appendix.
type poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared poolChain // Local P can pushHead/popHead; any P can popTail.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
其實(shí)結(jié)構(gòu)并不復(fù)雜膀藐,但是如果自己看的話有點(diǎn)懵屠阻。注意幾個(gè)細(xì)節(jié)就ok。
- local這里面真正的是[P]poolLocal其中P就是GPM模型中的P额各,有多少個(gè)P數(shù)組就有多大国觉,也就是每個(gè)P維護(hù)了一個(gè)本地的poolLocal。
- poolLocal里面維護(hù)了一個(gè)private一個(gè)shared虾啦,看名字其實(shí)就很明顯了麻诀,private是給自己用的痕寓,而shared的是一個(gè)隊(duì)列,可以給別人用的蝇闭。注釋寫(xiě)的也很清楚呻率,自己可以從隊(duì)列的頭部存然后從頭部取,而別的P可以從尾部取呻引。
- victim這個(gè)從字面上面也可以知道礼仗,幸存者嘛,當(dāng)進(jìn)行g(shù)c的stw時(shí)候逻悠,會(huì)將local中的對(duì)象移到victim中去藐守,也就是說(shuō)幸存了一次gc,
1. Get的邏輯其實(shí)非常清晰:
- 如果 private 不是空的蹂风,那就直接拿來(lái)用
- 如果 private 是空的,那就先去本地的shared隊(duì)列里面從頭 pop 一個(gè)
- 如果本地的 shared 也沒(méi)有了乾蓬,那 getSlow 去拿惠啄,其實(shí)就是去別的P的 shared 里面偷,偷不到回去 victim 幸存者里面找
- 如果最后都沒(méi)有任内,那就只能調(diào)用 New 方法創(chuàng)建一個(gè)了
2. Put邏輯就很簡(jiǎn)單了:
- 如果 private 沒(méi)有撵渡,就放在 private
- 如果 private 有了,那么就放到 shared 隊(duì)列的頭部
在看一個(gè)例子:
Put之后GC后Get
var pool = sync.Pool{
New: func() interface{} {
return "123"
},
}
func main() {
t := pool.Get().(string)
fmt.Println(t)
pool.Put("321")
pool.Put("321")
pool.Put("321")
pool.Put("321")
runtime.GC()
time.Sleep(1 * time.Second)
t2 := pool.Get().(string)
fmt.Println(t2)
runtime.GC()
time.Sleep(1 * time.Second)
t2 = pool.Get().(string)
fmt.Println(t2)
}
---輸出:
123
321
123
思考:
- 什么情況下適合使用sync.Pool呢死嗦?
- sync.Pool的對(duì)象什么時(shí)候會(huì)被回收呢趋距?
- sync.Pool是如何實(shí)現(xiàn)線程安全的?