😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go真实面试题汇总系列

    • 《Mutex篇》
  • 宝典内容

    • 15. WaitGroup的坑
    • 18. 读写锁底层是怎么实现的
    • 34. go 的锁是可重入的吗?
    • 120. go中的互斥锁:正常、饥饿状态,读写锁中写操作如何阻止读操作?
    • 180. golang 的 waitGroup 用法
    • 225. golang如何知道或者检测死锁
    • 226. 怎么处理锁分段
    • 241. 互斥锁的底层实现
    • 289. go标准库的mutex介绍
    • 366. golang的锁有了解吗?
    • 368. go标准库的mutex介绍

😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

289. go标准库的mutex介绍


企业题库解析小组

题目来源:知乎

# 答案:重拾

Go 号称是为了高并发而生的,在高并发场景下,势必会涉及到对公共资源的竞争。当对应场景发生时,我们经常会使用 mutex 的 Lock() 和 Unlock() 方法来占有或释放资源。

mutex 状态标志位

mutex 的 state 有 32 位,它的低 3 位分别表示 3 种状态:唤醒状态、上锁状态、饥饿状态,剩下的位数则表示当前阻塞等待的 goroutine 数量。

mutex 会根据当前的 state 状态来进入正常模式、饥饿模式或者是自旋。

mutex 正常模式

当 mutex 调用 Unlock() 方法释放锁资源时,如果发现有等待唤起的 Goroutine 队列时,则会将队头的 Goroutine 唤起。

队头的 goroutine 被唤起后,会调用 CAS 方法去尝试性的修改 state 状态,如果修改成功,则表示占有锁资源成功。

mutex 饥饿模式

由于上面的 Goroutine 唤起后并不是直接的占用资源,还需要调用 CAS 方法去尝试性占有锁资源。如果此时有新来的 Goroutine,那么它也会调用 CAS 方法去尝试性的占有资源。

但对于 Go 的调度机制来讲,会比较偏向于 CPU 占有时间较短的 Goroutine 先运行,而这将造成一定的几率让新来的 Goroutine 一直获取到锁资源,此时队头的 Goroutine 将一直占用不到,导致饿死。

针对这种情况,Go 采用了饥饿模式。即通过判断队头 Goroutine 在超过一定时间后还是得不到资源时,会在 Unlock 释放锁资源时,直接将锁资源交给队头 Goroutine,并且将当前状态改为饥饿模式。

后面如果有新来的 Goroutine 发现是饥饿模式时, 则会直接添加到等待队列的队尾。

mutex 自旋

如果 Goroutine 占用锁资源的时间比较短,那么每次都调用信号量来阻塞唤起 goroutine,将会很浪费资源。

因此在符合一定条件后,mutex 会让当前的 Goroutine 去空转 CPU,在空转完后再次调用 CAS 方法去尝试性的占有锁资源,直到不满足自旋条件,则最终会加入到等待队列里。

自旋的条件如下:

  • 还没自旋超过 4 次
  • 多核处理器
  • GOMAXPROCS > 1
  • p 上本地 Goroutine 队列为空

可以看出,自旋条件还是比较严格的,毕竟这会消耗 CPU 的运算能力。

mutex 的 Lock() 过程

首先,如果 mutex 的 state = 0,即没有谁在占有资源,也没有阻塞等待唤起的 goroutine。则会调用 CAS 方法去尝试性占有锁,不做其他动作。

如果不符合 m.state = 0,则进一步判断是否需要自旋。

当不需要自旋又或者自旋后还是得不到资源时,此时会调用 runtime_SemacquireMutex 信号量函数,将当前的 goroutine 阻塞并加入等待唤起队列里。

当有锁资源释放,mutex 在唤起了队头的 goroutine 后,队头 goroutine 会尝试性的占有锁资源,而此时也有可能会和新到来的 goroutine 一起竞争。

当队头 goroutine 一直得不到资源时,则会进入饥饿模式,直接将锁资源交给队头 goroutine,让新来的 goroutine 阻塞并加入到等待队列的队尾里。

对于饥饿模式将会持续到没有阻塞等待唤起的 goroutine 队列时,才会解除。

Unlock过程

mutex 的 Unlock() 则相对简单。同样的,会先进行快速的解锁,即没有等待唤起的 goroutine,则不需要继续做其他动作。

如果当前是正常模式,则简单的唤起队头 Goroutine。如果是饥饿模式,则会直接将锁交给队头 Goroutine,然后唤起队头 Goroutine,让它继续运行。

  • 答案:重拾