2 minutes
Golang Sync
这个是对之前分析的 mutex、rwmutex、condition、semaphore的回顾和总结.
论文参考
- https://swtch.com/semaphore.pdf
谈到 plan9 使用 user-space semaphore 代替 多进程协调中的flag状态的 spin lock.
user-space semaphore 使用一个字段表示用户状态下的sema值, 0->1 就是非竞态. 竞态情况下, 使用 内核的 QLock 排队锁 实现.
其中, 谈到 多进程的内核中, 自旋锁是可以的, 因为锁会被很快释放. 但是在用户态, 自旋锁状态下, 需要不断调度自己 + sleep, 其实什么也没有做.
golang中提供给用户的 Mutex 就是参照这个实现的, 先用 state 表示状态, 初始状态下直接获取锁, 并发争抢的时候, 进入semaRoot的逻辑, 形成sudog队列; 非抢占情况下, 只是简单的一次cas.
futex: redhat的文章 连接不上不去, 参照man
概念解析
-
sudog: goroutine 的 wait的表示, channel和 sudog是多对多的, 一个goroutine可能阻塞在多个对象上, 一个对象可能有多个 goroutine阻塞着, 使用 sudog解耦.
-
内部mutex: golang自定义的mutex实现, 通过对指针地址操作实现. 最差情况下, 是系统的semaphore. 采用了优化策略, cas -> 空循环 -> os yield -> semaphore. 进入semaphore状态的时候, 是多线程争抢, 存在线程排队的现象, 使用m的字段nextwaitm实现链表排队, 支持抢占.
-
semaRoot: 依赖内部的mutex实现的锁, 使用平衡树维护被锁的地址, 阻塞在同一个地址的sudog被维护在一个叶子上, sudog通过链表维护. 但是只维护了 251 个 semaRoot, 也就是说最多同时锁住 251 个地址.
-
Mutex实现: 直接依赖 semaRoot 实现.
-
cond实现: 依赖内部mutex提供锁的语义, 使用notifyList 维护等待的goroutine. 避免Wait + Signal + Broadcast竞争. 还依赖外部Mutex实现ticket生成的锁
-
syncmap:
-
rwmutex:
-
pool: 每个p都有一个private成员 以及一个 shared 链表, 每次获取, 先获取p本地的private成员, 没有, 就遍历p, 尝试获取shared链表, 还是没有, 就生成一个。但是,每次gc都会清除每个p的private和shared成员
问题:
对比 cond.go 和 Mutex, 因为Mutex更多的是 外部优化, 这里直接对比 cond.go 和 semaRoot.
cond.go 使用了 notifyList, 但是 semaRoot使用 平衡树 + goroutine链表 实现. cond.go 使用head、tail维护sudog链表, semaRoot使用sudog本身的next字段维护, 为什么有这个差异呢?
1. condition提供了 Wait + Signal + Broadcast 的语义, 实现上, 就是支持添加一个等待者、释放一个等待者、释放所有等待者, 并且Wait是放在列表尾部, Signal是在头部.
3. condition的notifyList用自己的地址 作为 内部mutex锁的地址
4. condition的notifyList 使用 head、tail 两个sudog 维护列表. O(1)复杂度
3. Mutex 提供了 Lock + UnLock 的语义, 借助semaRoot, 默认插入链表的最后面, 也支持插入到前面, 删除的时候, 肯定是删除的头部.
4. Semaroot 使用内部的 mutex 锁住 root 的lock,[ 这样, root 只有251个, Mutex很多的话, 并且lock/ublock比较频繁的话, 抢锁只会在 指定的数量上 的lock竞争. (condition做不到) … ]
4. SemaRoot 只用了一个 sudog 实现了 头部和尾部, 通过sudog的 waitlink 和 waittail.O(1) 复杂度
5. 所以, 从操作的特征上, 两者是类似的. Mutex借助 semaroot, 插入可以定制化(前面或者后面), 都是O(1), 删除的都是头部. cond.go 中 notifyList 是 插入尾部, 删除头部, 也是O(1).
6. 因为 sudog 给 semaRoot 开了后门, 所以只需要一个就可以了.
6. semaRoot 进行了锁的优化.
看下 commit 信息
关于优化点:
- cond的实现, 本身就需要 Mutex提供互斥语义, 但是只是提供了 生成ticket 的锁的语义. 在 Signal + Broadcast, 并没有 Mutex 加锁的操作, 是依靠 notifyList + 内部 mutex 实现的. 那么, 就一个优化点而言, cond.go 直接依赖 内部的mutex是否可以? 这样, 就避免多依赖一个 Mutex了, 实现能够更干净.
268 Words
2019-06-02 08:54 +0800