互斥锁

mutex的底层是通过atmoic原子操作来lock和unlock
在这里插入图片描述
在这里插入图片描述

lock()和unlock()通过对资源枷锁解锁来避免争抢,同一时刻只能有一个goroutine对资源进行操作,等待的goroutine通过自旋和等待队列老获得锁
在这里插入图片描述

读写锁

当读的协程远远大于写的协程数,采用互斥锁不免影响性能,此时可采用读写锁
在这里插入图片描述
在这里插入图片描述
有1000次的读和10次的写操作,在互斥锁的情况下的耗时1.4s
在这里插入图片描述

将读的锁换成读写锁
在这里插入图片描述
在这里插入图片描述
耗时是毫秒级的,以上很好的说明读多于写的场景,读写锁与互斥锁的性能差别

sync.Once()

Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。

sync.Once只有一个Do方法,其签名如下:

func (o *Once) Do(f func()) {}

备注:如果要执行的函数f需要传递参数就需要搭配闭包来使用。只有一个Do()方法

在这里插入图片描述
场景 比如有一个函数从文件读取图片,保存在map里面,调用方调用时,先检查是否加载,没有加载的话先加载,再返回给调用方对应的图片,看起来没有问题,实际在并发的时候,可能同时有多个goroutine检测到还没有加载,就都去加载,这样就出现资源的浪费
在这里插入图片描述
sync.once内部包含bool值,记录sync.once是否被执行过

sync.map

Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。

Load(key interface{}) (value interface{}, ok bool)
//通过提供一个键key,查找对应的值value,如果不存在,则返回nil。ok的结果表示是否在map中找到值
 
Store(key, value interface{})
//这个相当于是写map(更新或新增),第一个参数是key,第二个参数是value
 
LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
//通过提供一个键key,查找对应的值value,如果存在返回键的现有值,否则存储并返回给定的值,如果是读取则返回true,如果是存储返回false
 
Delete(key interface{})
//通过提供一个键key,删除键对应的值
 
Range(f func(key, value interface{}) bool)
//循环读取map中的值。
//因为for ... range map是内置的语言特性,所以没有办法使用for range遍历sync.Map, 但是可以使用它的Range方法,通过回调的方式遍

eg:

package main

import (
    "fmt"
    "sync"
)

func main()  {
    var m sync.Map
    // 1. 写入
    m.Store("qcrao", 18)
    m.Store("stefno", 20)

    // 2. 读取
    age, _ := m.Load("qcrao")
    fmt.Println(age.(int))

    // 3. 遍历
    m.Range(func(key, value interface{}) bool {
        name := key.(string)
        age := value.(int)
        fmt.Println(name, age)
        return true
    })

    // 4. 删除
    m.Delete("qcrao")
    age, ok := m.Load("qcrao")
    fmt.Println(age, ok)

    // 5. 读取或写入
    m.LoadOrStore("stefno", 100)
    age, _ = m.Load("stefno")
    fmt.Println(age)
}

在这里插入图片描述

总结:

1 sync.map 是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度。

2 通过读写分离,降低锁时间来提高效率,适用于读多写少的场景。

3 Range 操作需要提供一个函数,参数是 k,v,返回值是一个布尔值:ffunc(key, value interface{}) bool。

4 调用 Load 或 LoadOrStore 函数时,如果在 read 中没有找到 key,则会将 misses 值原子地增加 1,当 misses 增加到和 dirty 的长度相等时,会将 dirty 提升为 read。以期减少“读 miss”。

5新写入的 key 会保存到 dirty 中,如果这时 dirty 为nil,就会先新创建一个 dirty,并将 read 中未被删除的元素拷贝到 dirty。 当 dirty 为 nil 的时候,read 就代表 map 所有的数据;当 dirty 不为 nil 的时候,dirty 才代表 map 所有的数据。

查看源码分析

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐