16、Go語言同步4-sync包的其他API
- 1、Once 隻執行一次
- 2、對象池 sync.Pool
1、Once 隻執行一次
sync包除了提供了互斥鎖、讀寫鎖、條件變量外,還提供了一個結構體:
sync.Once
,負責隻執行一次,也即全局唯一操作。
使用方式如下:
var once sync.Once
once.Do(func(){}) // Do方法的有效調用次數永遠是1
sync.Once
的典型應用場景是隻執行一次的任務,如果這樣的任務不适合在init函數中執行,該結構體類就會派上用場。
sync.Once内部使用了“衛述語句、雙重檢查鎖定、共享标記的原子操作”來實作
Once
功能。
once示例:
package main
import (
"fmt"
"sync"
)
type Person struct {
Name string
Age int
}
func (p *Person)Grown() {
p.Age += 1
}
func main() {
var once sync.Once
var wg sync.WaitGroup
p := &Person{
"比爾",
0,
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
once.Do(func(){
p.Grown()
})
wg.Done()
}()
}
wg.Wait()
fmt.Println("年齡是:", p.Age) // 隻會輸出 1
}
2、對象池 sync.Pool
sync.Pool
類型可以視為臨時值的容器,該容器具備自動伸縮、高效特性,同時也是并發安全的,其方法有:
- Get:從池中取出一個interface{}類型的值
- Put:把一個interface{}類型的值存于池中
注意:
- 如果池子從未Put過,其New字段也沒有被指派一個非nil值,那麼Get方法傳回結果一定是nil。
- Get擷取的值不一定存在于池中,如果Get到的值存在于池中,則該值Get後會被删除
對象池原理:
- 對象池可以把内部的值産生的壓力分攤,即專門為每個操作它的協程關聯的P建立本地池。Get方法被調用時,先從本地P對象的本地私有池和本地共享池擷取值,如果擷取失敗,則從其他P的本地私有池偷取一個值傳回,如果依然沒有擷取到,會依賴于目前對象池的值生成函數。注意:生産函數産生的值不會被放到對象池中,隻是起到傳回值作用
- 對象池的Put方法會把參數值存放到本地P的本地池中,每個P的本地共享池中的值,都是共享的,即随時可能會被偷走
- 對象池對垃圾回收友好,執行垃圾回收時,會将對象池中的對象值全部溢出
應用場景:存儲被配置設定了但是未被使用,未來可能會被使用的值,以減少GC的壓力。
案例:由于fmt包總是使用一些
[]byte
對象,golang為期建立了一個臨時對象池,存放這些對象,需要的時候,從pool中取,拿不到則配置設定一分,這樣就能避免一直生成
[]byte
,垃圾回收的效率也高了很多。
// 一個[]byte的對象池,每個對象為一個[]byte
var bytePool = sync.Pool{
New: func() interface{} {
b := make([]byte, 1024)
return &b
},
}
func main() {
a := time.Now().Unix()
// 不使用對象池
for i := 0; i < 1000000000; i++{
obj := make([]byte,1024)
_ = obj
}
b := time.Now().Unix()
// 使用對象池
for i := 0; i < 1000000000; i++{
obj := bytePool.Get().(*[]byte)
_ = obj
bytePool.Put(obj)
}
c := time.Now().Unix()
fmt.Println("without pool ", b - a, "s")
fmt.Println("with pool ", c - b, "s")
}
// without pool 34 s
// with pool 24 s