跳到主要内容

同步4-sync包的其他API

一 Once 只执行一次

sync包提供了互斥锁、读写锁、条件变量等常见并发场景需要的API。sync还有一些其他API,如结构体: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

}

二 对象池 sync.Pool

sync.Pool可以作为临时值的容器,该容器具备自动伸缩、高效特性,同时也是并发安全的,其方法有:

  • Get:从池中取出一个值,类型为interface{}
  • Put:存储一个值到池中,存储的值类型为interface{}

使用示例:

	p := &sync.Pool{
New: func() interface{} {
return 0
},
}

a := p.Get().(int)
p.Put(1)
b := p.Get().(int)
fmt.Println(a, b) // 0 1

注意:

  • 如果池子从未Put过,其New字段也没有被赋值一个非nil值,那么Get方法返回结果一定是nil。
  • Get获取的值不一定存在于池中,如果Get到的值存在于池中,则该值Get后会被删除

对象池原理:

  • 对象池可以把内部的值产生的压力分摊,即专门为每个操作它的协程关联的P建立本地池。Get方法被调用时,先从本地P对象的本地私有池和本地共享池获取值,如果获取失败,则从其他P的本地私有池偷取一个值返回,如果依然没有获取到,会依赖于当前对象池的值生成函数。注意:生产函数产生的值不会被放到对象池中,只是起到返回值作用
  • 对象池的Put方法会把参数值存放到本地P的本地池中,每个P的本地共享池中的值,都是共享的,即随时可能会被偷走
  • 对象池对垃圾回收友好,执行垃圾回收时,会将对象池中的对象值全部溢出

应用场景:sync.Pool的定位不是做类似连接池的东西,仅仅是增加对象重用的几率,减少gc的负担,而开销方面也不是很便宜的。

案例:由于fmt包总是使用一些[]byte对象,可以为其建立了一个临时对象池,存放这些对象,需要的时候,从pool中取,拿不到则分配一份,这样就能避免一直生成[]byte,垃圾回收的效率也高了很多。

示例:

// 声明[]byte的对象池,每个对象为一个[]byte
var BytePool = sync.Pool{
New: func() interface{} {
b := make([]byte, 1024)
return &b
},
}

func main() {

time1 := time.Now().Unix()

// 不使用对象池创建 10000000
obj1 := make([]byte, 1024)
for i := 0; i < 100000000; i++ {
obj1 = make([]byte, 1024)
_ = obj1
}

time2 := time.Now().Unix()

// 使用对象池创建 10010000
obj2 := BytePool.Get().(*[]byte)
for i := 0; i < 100000000; i++ {
obj2 = BytePool.Get().(*[]byte)
BytePool.Put(obj2)
_ = obj2
}

time3 := time.Now().Unix()

fmt.Println("without pool: ", time2-time1, "s") // 16s
fmt.Println("with pool: ", time3-time2, "s") // 1s
}