go常用包——sync

内容

  • Atomic
  • Mutex
  • RWMutex
  • WaitGroup
  • Once
  • Sync.Map
  • Sync.Pool
  • Cond

Atomic

go中atomic 包实现原子操作

  • 基本类型的原子操作有6种:int32, int64, uint32, uint64, uintptr, unsafe.Pinter;操作类型有5种:增减, 比较并交换, 载入, 存储,交换
  • Value提供任意类型的原子操作,操作类型有:载入和存储
基本数据类型:
    //增减操作
    var a int32;
    //增操作
    new_a := atomic.AddInt32(&a, 1);

    //减操作
    new_a = atomic.AddInt32(&a, -1);
 
    //CAS(Compare And Swap)比较并交换操作
    //函数名以CompareAndSwap为前缀,并具体类型名
    //函数会先判断参数一指向的值与参数二是否相等,如果相等,则用参数三替换参数一的值。
    var b int32;
    atomic.CompareAndSwapInt32(&b, 0, 3);
 
    //载入操作
    //当我们对某个变量进行读取操作时,可能该变量正在被其他操作改变,或许我们读取的是被修改了一半的数据。
    //所以我们通过Load这类函数来确保我们正确的读取
    //函数名以Load为前缀,加具体类型名
    var c int32;
    tmp := atomic.LoadInt32(&c);

    //存储操作
    //与载入函数相对应,提供原子的存储函数; 函数名以Store为前缀,加具体类型名
    var d int32;
    //存储某个值时,任何CPU都不会都该值进行读或写操作
    //存储操作总会成功,它不关心旧值是什么,与CAS不同
    atomic.StoreInt32(&d, 666);

任意类型:
提供了两个方法,保证原子的存储和读取(并发操作时,保证不会读到正在写入到一半的值)
1 func (v *Value) Load() (x interface{})
2 func (v *Value) Store(x interface{})

    type AtomicStruct struct {
        Age int
        name string
    }
    config := atomic.Value{} // 1 声明一个value类型变量,相当于一个容器
    config.Store(&AtomicStruct{})  // 2 说明这个容器存储的是AtomicStruct 指针类型,一旦说明后,后续的store操作只能是这个类型

    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func(i int) {
            defer wg.Done()
            if i == 5 {
                name := "name" + string(i)
                config.Store(&AtomicStruct{Age: i, name: name})
            }
            fmt.Println(config.Load())
        }(i)
    }
    wg.Wait()

Mutex

互斥锁: go中通过sync.Mutex的零值来表示一个没有被锁定的互斥量,开箱即用,申明一个变量就可以用,但是如果把互斥量当做入参传入到函数中使用时,只能传递指针,不能是值传递,值传递是拷贝,不能达到锁住的作用。

var x int 
func main()  {
var m sync.Mutex
add(&m, 2)
}

// 同步的做甲方
func add(m *sync.Mutex, value int){
m.Lock()
x = x + value
m.Unlock()
}

RWMutex

读写锁: go中通过sync.RWMutex的零值来表示一个读写锁实例,读写锁中有两对方法,分别是读锁定和读解锁、写锁定和写解锁

  • 规则:读写锁下,多个写操作之间是互斥的,写操作和读操作之间也是互斥的,但是多个读操作之间不存在互斥关系
rw.Lock() // 写锁定
rw.Unlock()
rw.RLock() // 读锁定
rw.RUnlock()

func main()  {
   var rw sync.RWMutex
   go func() {
      rw.RLock()
      println(1)
      time.Sleep(2*time.Second)
      rw.RUnlock()
      println(2)
   }()
   time.Sleep(1*time.Second)
   println(3)
    // 被堵在这里,因为协程中 rw.RLock()加了读锁,会阻塞写锁,不会堵塞其他协程获取读锁,rw.RUnlock()执行后,释放了读锁,同样也会释放写锁,前提是已经没有任何其他的读锁了;如果是先执行rw.Lock(),同样也会堵塞其他写锁和读锁,rw.Ulock()会把读锁和写锁全部释放
   rw.Lock()
   println(4)
}

WaitGroup

  • wg会阻塞在wait()方法上,等待一组协程执行结束,同样是开箱即用,在函数中当做入参传递时,记住传递指针
func main()  {
   var wg sync.WaitGroup
   wg.Add(1)
   go func() {
      println(1)
      wg.Done()
   }()

   //主协程阻塞在这里,必定会等协程中执行完毕后,才会执行打印2
   wg.Wait()
   println(2)
}

Once

  • sync.Once 同样开箱即用,申明即可, Do方法接收一个无参数、无结果的函数作为参数,保证这个方法只执行一次
  • 原理是Once结构体内维护了一个无符号的变量done,当执行一次func后会原子的加1,如果done不会0时,就不会执行func了
 var once sync.Once //同样开箱即用
// Do方法接收一个无参数、无结果的函数作为参数,保证这个方法只执行一次
 Once.Do(func(){
    fmt.println(“func方法只执行一次")
})

// 原理是Once结构体内维护了一个无符号的变量done,当执行一次func后会原子的加1,如果done不会0时,就不会执行func了

func (o *Once) Do(f func()) {
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}
func (o *Once) doSlow(f func()) {
   o.m.Lock()
   defer o.m.Unlock()
   if o.done == 0 {
      defer atomic.StoreUint32(&o.done, 1)
      f()
   }
}

sync.Map

  • 并发安全的map
func main(){
    var sm sync.Map
    // 写入
    sm.Store("a", 1)
    sm.Store("b", 2)
    sm.Store("c", 3)

    // 取出
    v, _ := sm.Load("a")
    fmt.Println(v.(int))

    // 删除
    sm.Delete("b")

    // 存在则获取 不存在则添加
    sm.LoadOrStore("d", 4)

    // 遍历
    sm.Range(func(key, value interface{}) bool {
        fmt.Println(key.(string), value.(int))
        return true
    })
}

Sync.Pool

  • Pool用来做一个临时对象池,当某个对象会被经常创建或是在并发场景下多个协程会会创建相同的对象,此时可以考虑用sync.Pool来优化性能,避免大量对象的创建 销毁引起的GC问题。
  • Pool只有两个方法,Get和Put, Get从对象池中获取对象,如果不存在则用初始化对象池是赋予的New方法创建一个对象返回, Put方法用于当用完对象后,把对象归还到对象池,如果对象需要保持初始化状态,则用完对象后,应该对对象做一些清零的逻辑,然后在归还到池子中,否则下一次get是,获取到的对象会保存了上一次put的记录;
  • Pool不适于做连接池之类的,因为pool会在GC是被回收的;
  • Pool是并发安全的;
    举个例子
func SyncPoolExample()  {
    pool := sync.Pool{
        New: func() interface{} {
            fmt.Println("aa")
            return People{}
        },
    }
    for i:=0; i < 3; i++ {
        go func() {
            people := pool.Get().(People)
            fmt.Println(people)
            people.name = "li"
            fmt.Println(people)
            people.name = ""
            pool.Put(people)
        }()
        time.Sleep(time.Second * 2)
    }
    time.Sleep(time.Second * 10)
}

aa
{0 }
{0 li}
aa
{0 }
{0 li}
{0 }
{0 li}
当协程还没有归还对象到池子里时,如果其他协程此时来get,则就会新建一个

//example 2
func main() {
    pool := sync.Pool{
        New: func() interface{} {
            b := make([]byte, 0, 1024)
            return b
        },
    }
    b := pool.Get().([]byte)
    b = append(b, 0xff)

    b = b[:0] // 用完后,清空[]byte,在归还池子
    pool.Put(b)
}

实现原理:

  • 深度解密Go语言之sync.Pool: 深度解密 Go 语言之 sync.Pool
  • 我所理解的Sync Pool: https://www.haohongfan.com/post/2019-05-26-sync-pool/
  • go sync.pool []byte导致grpc解包异常 [http://xiaorui.cc/archives/5969]

Cond

条件变量,类似java object中的wait notify和notifyAll方法,Cond也提供了三个方法Wait、Singal、Broadcast

func SyncCondExample(){
    cond := sync.NewCond(new(sync.Mutex))
    for i := 0; i < 10; i++ {
        go func(i int) {
            cond.L.Lock()
            cond.Wait()
            fmt.Println("goroutine: ", i)
            cond.L.Unlock()
        }(i)
    }
    fmt.Println("all goroutines wait...")
    time.Sleep(time.Second * 3)
    //  按等待顺序 释放一个
    //cond.Signal()
    // 释放所有
    cond.Broadcast()
    time.Sleep(time.Second * 3)
}

all goroutines wait...
goroutine:  4
goroutine:  8
goroutine:  5
goroutine:  1
goroutine:  0
goroutine:  7
goroutine:  3
goroutine:  2
goroutine:  6
goroutine:  9

你可能感兴趣的:(go常用包——sync)