go并发编程-路线总结1

75 阅读5分钟

知识储备

临界区

在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。

CAS

  • CAS 指令将给定的值和一个内存地址中的值进行比较,如果它们是同一个值,就使用新值替换内存地址中的值,这个操作是原子性的
  • 原子性保证这个指令总是基于最新的值进行计算,如果同时有其它线程已经修改了这个值,那么,CAS 会返回失败。

sync

mutex

当一个 goroutine 获得锁后, 其它请求锁的goroutine 就会阻塞,直到锁被释放并且自己获得这个锁

Locker 接口

mutex实现了Locker接口

四个阶段

  • 初版
  • 给新人机会
  • 给给些机会
  • 解决饥饿

结构体

  • state
    • mutexLocked 持有锁标记
    • mutexWoken 唤醒标记
    • mutexStarving 饥饿标记
    • mutexWaiters 阻塞等待的waiter数量
  • sema:信号量变量,用来控制等待goroutine的阻塞休眠和唤醒

模式

正常模式

  • 先入先出,被唤醒的 waiter 和新来的 goroutine 进行竞争
  • 唤醒的waiter获取不到锁,插入到队列的前面。新来的 goroutine获取不到锁,插入到队列尾部
  • waiter获取不到锁超过1毫秒,进入饥饿模式

饥饿模式

  • 锁交给队列最前面的waiter
  • 新来的goroutine不会获取锁,直接插入队列尾部
  • waiter是队列中的最后一个了或者waiter的等待时间小于 1 毫秒,进入正常模式

易错场景

  • Lock/Unlock 不是成对出现
  • Copy 已使用的 Mutex
  • 重入
  • 死锁
    • 互斥
    • 持有和等待
    • 不可剥夺
    • 环路等待

RWMutex

某一时刻只能由任意数量的 reader 持有,或者是只被单个的 writer 持有

reader和writer优先级

  • 写优先
  • 请求的writer到来,如果已经有一些reader请求了锁的话,writer会等待已经存在的reader都释放锁之后才获取到锁,后面来的新reader要等writer执行完后才会获得锁 。

结构体

  • 5个字段
    • w Mutex // 互斥锁解决多个writer的竞争
    • writerSem uint32 // writer信号量
    • readerSem uint32 // reader信号量
    • readerCount int32 // 记录当前 reader 的数量(以及是否有 writer 竞争锁)
    • readerWait int32 // 记录 writer 请求锁时需要等待 read 完成的 reader 的数量
  • 一个常量:const rwmutexMaxReaders = 1 << 30=1073741824//定义了最大的 reader 数量

易错场景

  • 不可复制
  • 重入导致死锁
    • writer 重入调用 Lock 的时候,就会出现死锁的现象
    • 在 reader 的读操作时调用 writer 的写操作
    • writer 依赖活跃的 reader -> 活跃的 reader 依赖新来的 reader -> 新来的 reader依赖 writer。
  • 释放未加锁的 RWMutex

WaitGroup

用于等待一组goroutine完成执行的同步原语

方法

  • Add:用来设置 WaitGroup 的计数值
  • Done:用来将 WaitGroup 的计数值减 1,其实就是调用了 Add(-1)
  • Wait:调用这个方法的 goroutine 会一直阻塞,直到 WaitGroup 的计数值变为 0

结构体

  • noCopy
    • 辅助 vet 工具检查是否通过 copy 赋值这个 WaitGroup实例。不能在第一次使用之后复制使用
  • state1
    • waiter数
    • 计数值
    • 信号量

常见错误

  • 计数器设置为负值
    • 调用 Add 的时候传递一个负数
    • 调用 Done 方法的次数过多,超过了 WaitGroup 的计数值
  • 不期望的 Add 时机。 并发Wait 和 Add,会出现 panic。

Once

用来执行且仅仅执行一次动作,常常用于单例对象的初始化场景

方法

  • Do:只有第一次调用会执行

结构体

  • done uint32 标记是否已经执行过Do方法
  • m 保证只有一个 goroutine 执行Do方法

错误

  • 死锁,Do方法里面再次调用Do方法
  • 未初始化,Do方法执行失败不会再次执行

Cond

等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine或者所有的 goroutine 都会被唤醒执行(等待-条件-通知)

方法

  • Broadcast:唤醒等待队列中的所有waiter
  • Signal:唤醒等待队列中的第一个waiter
  • wait:
    • 把调用者放到等待队列中并阻塞,直到被Broadcast或者Signal唤醒并从等待队列中删除
    • 必须要持有锁(一般Mutex 或者 RWMutex)

结构体

  • L 绑定的锁
  • notifyList 等待 / 通知的队列
  • copyChecker 辅助结构,检查 Cond 是否被复制使用

常见错误

  • 调用 Wait 的时候没有加锁
  • 只调用了一次 Wait,没有检查等待条件是否满足,结果条件没满足,程序就继续执行了( waiter 被唤醒不等于等待条件被满足)

Map

并发安全、键值对、无序

key类型

  • K 必须是可比较的
  • 不可比较类型:slice、map、函数、struct包含slice也不可以当key

方法

  • Store
  • Load
  • Delete
  • Range

优点

  • 空间换时间
  • 优先从read字段增删查改,读取不需要加锁
  • 动态调整
  • double-checking
  • 延迟删除

Pool

来缓存一组可独立访问的临时对象,避免反复创建销毁带来的性能损耗

特点

  • 并发安全
  • 不可以在使用之后复制

方法

  • New:创建新的元素
  • Get:取走元素
  • Put:返回元素

结构体

  • noCopy
  • local:用来存储当前主要的空闲可用的元素
  • localSize
  • victim:用来存储空闲的元素。垃圾回收时,把 victim 中的对象移除,然后把 local 的数据给 victim,local 就会被清空
  • victimSize
  • New

  • 内存泄露
  • 内存浪费