golang sync模块

sync.Mutex/RWMutex

  • channel一般用于解决逻辑层面的并发处理,而锁用来保护局部变量的数据安全
    golang sync模块
    文章图片

  • 重复加锁会引发死锁,重复解锁会引发panic
  • sync.Mutex作为参数时的时候需要传指针,不然就是拷贝,会引起加锁失败
  • 作为嵌入类型时应该嵌入指针
    • 因为调用值接受者的方法是拷贝
type data struct { sync.Mutex }func (d data) test(s string) { d.Lock() defer d.Unlock() for i :=0; i < 3; i++ { fmt.Println(s) time.Sleep(time.Second) } }// 调用 var d data go func() {d.test("read")}() go func() {d.test("write")}()// 输出:write和read交替输出,并没有实现加锁 // 改为指针接受者或者指针内嵌可以解决该锁失效问题

sync.WaitGroup
  • 适合实现一对多的 goroutine 协作流程
  • sync.waitGroup作为参数时,应该传入指针,或者 Done()方法
  • 计数器的值不能小于0, 这样会引发一个 panic
    • 不适当地调用这类值的Done方法和Add方法都会如此
func main() { var wg sync.WaitGroup wg.Add(2) go f(&wg) go g(wg.Done) wg.Wait() }func f(wg *sync.WaitGroup) { defer wg.Done() //do something }func g(done func()) { defer done() //do something }

sync.Map
  • 内置map多线程不安全,多个线程同时写入map会panic
    • 并且该panic不能被recover
  • 解决方案
    • 使用sync.RWMutex加锁
      • 不推荐,锁消耗较大
    • 使用线程安全的 sync.Map
      • 适合读多写少
      • 用空间换时间,内存占用较大
      • 算法复杂度与map类型一样都是O(1)
    • 使用 concurrent map
      • 适合读写都频繁
      • 分段加锁map
      • 例如: https://github.com/orcaman/concurrent-map
golang sync模块
文章图片

  • sync.Map的键和值的类型都是interface{}
    • 由于这些键值的实际类型只有在程序运行期间才能够确定,所以无法在编译期对它们进行检查
    • 键类型不能是函数类型、字典类型和切片类型
    • 不正确的键值实际类型会引发 panic
实现原理
  • 本身确实也用到了锁,但是,它会尽可能地避免使用锁
  • sync.Map类型在内部使用了大量的原子操作来存取键和值,并使用了两个原生的map作为存储介质
  • 这两个原生字典一个被称为只读字典,另一个被称为脏字典
    • 空间换时间。通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响
    • 使用只读数据(read),避免读写冲突
    • 动态调整,miss次数多了之后,将dirty数据提升为read
    • 延迟删除。 删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据
    • 优先从read读取、更新、删除,因为对read的读取不需要锁
【golang sync模块】golang sync模块
文章图片

golang sync模块
文章图片

sync.Once
  • sync.Once只计算Do被调用的次数,而不是调用传入Do的唯一函数的次数
sync.Pool
  • 复用对象,例如切片等
  • 对象的缓存有效期为下一次GC之前
    • 生命周期受 GC 影响,不适合于做连接池等 golang sync模块
      文章图片
sync.Cond
  • 条件变量并不是被用来保护临界区和共享资源的,它是用于控制想要访问共享资源线程的顺序
    • 当共享资源的状态发生变化时,它可以被用来通知被互斥锁阻塞的线程
  • 条件变量是基于互斥锁或读写锁的,必须有他们的支持才能够起作用
  • 参考:https://www.kancloud.cn/mutouzhang/go/596828
sync/atomic
  • 执行速度要比其他的同步工具快得多,通常会高出好几个数量级
  • 但由于原子操作函数只支持非常有限的数据类型,所以在很多应用场景下,互斥锁往往是更加适合的
    • 操作系统层面只对针对二进制位或整数的原子操作提供了支持
      • 因为原子操作不能被中断,所以它需要足够简单,并且要求快速
      • 如果原子操作迟迟不能完成,而它又不会被中断,将会给计算机执行指令的效率带来巨大的负面影响
  • sync/atomic包中的函数可以做的原子操作有: 加法(add)、比较并交换(compare and swap,简称 CAS)、加载(load)、存储(store)和交换(swap)
    • CAS 操作,是有条件的交换操作,只有在条件满足的情况下才会进行值的交换

    推荐阅读