Go中的sync

462 阅读3分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

访问临界资源面临的问题

 // 定义全局变量
 var (
    x = 0
    wg sync.WaitGroup
 )
 ​
 // 访问临界资源
 func f1()  {
    for i := 0; i < 10000; i++ {
       x ++
    }
    wg.Done()
 }
 ​
 func main() {
 ​
    wg.Add(2)
    
    // 开启两个goroutine去访问临界资源
    go f1()
    go f1()
 ​
    wg.Wait()
    
    // 我们预期的结果应该为: 20000, 可是实际上结果并不是一个确定的数
    fmt.Println("x的值为: ", x)
 }

sync.Mutex

该锁为互斥锁

当一个goroutine获取到互斥锁, 可以访问临界区时, 其它goroutine等待 当前访问临界区的goroutine解开互斥锁时, 其它goroutine才可重新获取互斥锁

 // 定义全局变量
 var (
    x = 0
    wg sync.WaitGroup
 ​
    // 定义一个全局互斥锁
    lock sync.Mutex
 )
 ​
 // 访问临界资源
 func f1()  {
    for i := 0; i < 10000; i++ {
       // 在访问资源的时候加锁
       lock.Lock()
 ​
       x ++
 ​
       // 访问资源完之后解锁
       lock.Unlock()
    }
    wg.Done()
 }
 ​
 func main() {
 ​
    wg.Add(2)
 ​
    // 开启两个goroutine去访问临界资源
    go f1()
    go f1()
 ​
    wg.Wait()
 ​
    // 现在输出的结果就是我们预期的结果了
    fmt.Println("x的值为: ", x)
 }

sync.RWMutex

读锁定状态下: 可以再次加读锁定, 不能进行写锁定

写锁定状态下: 不能再次进行读锁定和写锁定

 // 定义全局变量
 var (
    x = 0
    wg sync.WaitGroup
    
    // 互斥锁
    //lock sync.Mutex
    
    // 读写锁
    rwLock sync.RWMutex
 )
 ​
 ​
 // 读操作
 func read()  {
    // 加读锁
    rwLock.RLock()
    time.Sleep(time.Millisecond * 10)
    
    // 解读锁
    rwLock.RUnlock()
 ​
    wg.Done()
 }
 ​
 // 写操作
 func write()  {
    // 加写锁
    rwLock.Lock()
    x++
    
    // 解写锁
    rwLock.Unlock()
 ​
    wg.Done()
 }
 ​
 func main() {
    now := time.Now()
 ​
    for i := 0; i < 100; i++ {
       wg.Add(1)
       go write()
    }
 ​
    for i := 0; i < 100; i++ {
       wg.Add(1)
       go read()
    }
 ​
    wg.Wait()
 ​
    // 使用互斥锁几次花费时间是: 1.078879827s 1.058491323s 1.067613451s
 ​
    // 使用读写锁几次花费时间是: 21.997394ms 22.495443ms 22.509435ms
    
    /*
    从运行结果来看, 它们之间的花费时间是有量级上的差距
    因此: 我们要在合适的时间, 使用合适的锁, 来编写我们的代码
    */
    fmt.Println(time.Now().Sub(now))
 ​
 }

sync.Once

一个Once变量只会执行一个Do函数

也就是说每一个要执行的函数, 都需要一个新的Once变量去调用

 var once1 sync.Once
 var once2 sync.Once
 ​
 func f1()  {
    fmt.Println("我是f1, 我被执行了")
 }
 ​
 func f2()  {
    fmt.Println("我是f2, 我被执行了")
 }
 ​
 func main() {
    once1.Do(f1)
    once2.Do(f2)
   
   // 下面的两个函数不会被执行
    once1.Do(f1)
    once2.Do(f2)
 }

sync.Once 源码分析

 /*
 Once是一个结构体类型
 里面有两个变量: 
 done, 用来标识函数do函数是否被执行
 m, 一个互斥锁
 */
 type Once struct {
    done uint32
    m    Mutex
 }
 ​
 /*
 先判断done标志, 决定是否执行f函数
 */
 func (o *Once) Do(f func()) {
     if atomic.LoadUint32(&o.done) == 0 {
     o.doSlow(f)
   }
 }
 ​
 /*
 先加锁, 在函数调用完之后, 更改done标志位, 然后解锁
 调用函数
 */
 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

 var m sync.Map
 ​
 // 存值
 m.Store("shaosiming", 18)
 m.Store("dasiming", 20)
 fmt.Printf("%#v \n", m)
 ​
 // 取值
 v, ok := m.Load("shaosiming")
 if ok {
    fmt.Println(v)
 }
 ​
 // 取值
 // 如果key存在, 返回对应的值和true, 不会修改原来的值
 // 如果key不存在, 返回false和存入的值, 并将键值对存入map
 v, ok = m.LoadOrStore("dasiming2", 22)
 if ok {
    fmt.Println(v)
 } else {
    fmt.Println(v)
    fmt.Println("map中没有对应的key, 所以存入该键值")
    v, ok = m.Load("dasiming2")
    if ok {
       fmt.Println(v)
    }
 }
 ​
 // 删除数据
 //m.Delete("dasiming3")
 ​
 // 如果key存在, 则删除对应的键值, 返回true
 // 如果key不存在, 删除失败, 返回false
 v, ok = m.LoadAndDelete("dasiming3")
 if ok {
    fmt.Println("删除数据成功, value: ", v)
 } else {
    fmt.Println("删除数据失败, value: ", v)
 }
 ​
 // map的遍历
 m.Range(func(key, value interface{}) bool {
    fmt.Println("key: ", key, " value: ", value)
    return true
 })