Mutex是go标准库中的互斥锁,用于处理并发场景下共享资源的访问冲突问题。

1. Mutex定义:

  1. // A Mutex is a mutual exclusion lock.
  2. // The zero value for a Mutex is an unlocked mutex.
  3. //
  4. // A Mutex must not be copied after first use.
  5. type Mutex struct {
  6. state int32 //表示当前锁的状态
  7. sema uint32 //信号量专用,用于阻塞/唤醒goroutine
  8. }
  9.  
  10. // A Locker represents an object that can be locked and unlocked.
  11. type Locker interface {
  12. Lock()
  13. Unlock()
  14. } 

state字段在这里有更丰富的含义,他是一个32位的整型。他的多重含义通过不同的位来表示:

在这里,

locked:表示这个锁是否被持有。

wokend:表示是否有唤醒的goroutine线程。

starving:表示是否处于饥饿状态。

waiterCount:表示等待该锁的goroutine的数量。

2. 加锁操作

  1. // Lock locks m.
  2. // If the lock is already in use, the calling goroutine
  3. // blocks until the mutex is available.
  4. func (m *Mutex) Lock() {
  5. // Fast path: grab unlocked mutex.
    // 如果当前处于未加锁状态,则直接加锁,并返回
    // CompareAndSwapINt32() 将指定的old值 0 与指定地址 &m.state中的值相比较,如果相等,将new值 mutexLocked赋值给指定地址 &m.state。
  6. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
  7. if race.Enabled {
  8. race.Acquire(unsafe.Pointer(m))
  9. }
  10. return
  11. }
  12. // Slow path (outlined so that the fast path can be inlined)
    // 否则,进入slow path
  13. m.lockSlow()
  14. }

  

  1. func (m *Mutex) lockSlow() {
    // 等待时间
  2. var waitStartTime int64
    // 是否处于饥饿状态
  3. starving := false
    // 是否有唤醒的goroutine
  4. awoke := false
    // 自旋迭代次数
  5. iter := 0
  6. old := m.state
  7. for {
  8. // Don't spin in starvation mode, ownership is handed off to waiters
  9. // so we won't be able to acquire the mutex anyway.
    // 如果当前锁被占有且处于非饥饿状态,就进入自旋;自旋次数达到上限,再进入下一步。需要注意的是,如果这是一个被唤醒的协程,还需要将唤醒标志位置成唤醒状态1.
  10. if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
  11. // Active spinning makes sense.
  12. // Try to set mutexWoken flag to inform Unlock
  13. // to not wake other blocked goroutines.
  14. if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
  15. atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
  16. awoke = true
  17. }
  18. runtime_doSpin()
  19. iter++
  20. old = m.state //再次获取锁的状态,之后会检查锁是否被释放了
  21. continue
  22. }
  23. new := old
  24. // Don't try to acquire starving mutex, new arriving goroutines must queue.
    // 自旋结束后,检查旧状态;如果是非饥饿状态,新的状态持有锁标志位置成 1
  25. if old&mutexStarving == 0 {
  26. new |= mutexLocked
  27. }
    // 检查旧状态,如果锁被占用或处于饥饿状态为1,waiter数量 +1;这个时候,协程只能进入等待队列。如果当前是饥饿状态,新来得协程什么也做不了,会直接沉睡。
  28. if old&(mutexLocked|mutexStarving) != 0 {
  29. new += 1 << mutexWaiterShift
  30. }
  31. // The current goroutine switches mutex to starvation mode.
  32. // But if the mutex is currently unlocked, don't do the switch.
  33. // Unlock expects that starving mutex has waiters, which will not
  34. // be true in this case.
    // 如果当前协程被唤醒后,认定是饥饿状态,并且目前锁是被占用状态;那么锁的饥饿标志位被设定为1.
  35. if starving && old&mutexLocked != 0 {
  36. new |= mutexStarving
  37. }
  38. if awoke {
  39. // The goroutine has been woken from sleep,
  40. // so we need to reset the flag in either case.
    // 协程被唤醒,需要将唤醒标志位清0,因为他要么继续沉睡,要么获取锁
  41. if new&mutexWoken == 0 {
  42. throw("sync: inconsistent mutex state")
  43. }
  44. new &^= mutexWoken
  45. }
  46. if atomic.CompareAndSwapInt32(&m.state, old, new) {
    // 如果当前锁的状态已经被释放,并且不是饥饿状态,那么当前协程直接占用锁,并返回
  47. if old&(mutexLocked|mutexStarving) == 0 {
  48. break // locked the mutex with CAS
  49. }
  50. // If we were already waiting before, queue at the front of the queue.
    // 如果没能抢到锁,就进入等待状态;被唤醒的重新沉睡的协程会被放在队列的首位,并且这里会开始计时,用于计算是否达到饥饿状态。新加入的自旋之后的协程会放在队列的尾部。
  51. queueLifo := waitStartTime != 0
  52. if waitStartTime == 0 {
  53. waitStartTime = runtime_nanotime()
  54. }
    // 阻塞等待,休眠当前goroutine等待并尝试获取信号量唤醒当前goroutine
  55. runtime_SemacquireMutex(&m.sema, queueLifo, 1)
    // 唤醒之后检查锁是否处于饥饿状态
  56. starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
  57. old = m.state
    // 如果锁已经处于饥饿状态,直接抢到锁,返回
  58. if old&mutexStarving != 0 {
  59. // If this goroutine was woken and mutex is in starvation mode,
  60. // ownership was handed off to us but mutex is in somewhat
  61. // inconsistent state: mutexLocked is not set and we are still
  62. // accounted as waiter. Fix that.
    // 如果当前协程被唤醒,欢迎之后检查锁是否处于饥饿状态;如果处于饥饿状态,直接抢到锁,返回
  63. if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
  64. throw("sync: inconsistent mutex state")
  65. }
    // 加锁,并将waiter数减1
  66. delta := int32(mutexLocked - 1<<mutexWaiterShift)
  67. if !starving || old>>mutexWaiterShift == 1 {
  68. // Exit starvation mode.
  69. // Critical to do it here and consider wait time.
  70. // Starvation mode is so inefficient, that two goroutines
  71. // can go lock-step infinitely once they switch mutex
  72. // to starvation mode.
    // 最后一个 waiter或者已经不饥饿了,清楚饥饿标记
  73. delta -= mutexStarving
  74. }
  75. atomic.AddInt32(&m.state, delta)
  76. break
  77. }
  78. awoke = true
  79. iter = 0
  80. } else {
  81. old = m.state
  82. }
  83. }
  84.  
  85. if race.Enabled {
  86. race.Acquire(unsafe.Pointer(m))
  87. }
  88. }

加锁操作如下:

1. 新来的goroutine直接抢占锁,抢占失败进入自旋(循环),自旋一定次数后还是没有抢到锁进入等待队列。这一步给占有CPU时间片的goroutine更多的机会,降低整体的性能消耗。

2. 当前协程被唤醒后,判断是否处于饥饿状态;

如果是非饥饿状态,那么和新来的协程走正常抢占锁的流程;因为被唤醒的协程不占用cpu时间片,经常抢不过新来的协程;如果协程超过1ms还没有获取到锁,就进入饥饿状态。

如果是饥饿状态,将锁的饥饿标志位设置为1;锁的持有者执行完任务后,会将饥饿状态的锁交给队列中的第一个协程;新来的协程不会去抢占锁,也不会spin,而会直接进入阻塞队列。

3. 解锁操作

  1. // Unlock unlocks m.
  2. // It is a run-time error if m is not locked on entry to Unlock.
  3. //
  4. // A locked Mutex is not associated with a particular goroutine.
  5. // It is allowed for one goroutine to lock a Mutex and then
  6. // arrange for another goroutine to unlock it.
  7. func (m *Mutex) Unlock() {
  8. if race.Enabled {
  9. _ = m.state
  10. race.Release(unsafe.Pointer(m))
  11. }
  12.  
  13. // Fast path: drop lock bit.
    // 给 m.state直接减 1, 如果等于0,表示没有其他goroutine在等待,可以直接返回。
  14. new := atomic.AddInt32(&m.state, -mutexLocked)
  15. if new != 0 {
  16. // Outlined slow path to allow inlining the fast path.
  17. // To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
    // 否则,需要通过信号量机制唤醒沉睡的goroutine
  18. m.unlockSlow(new)
  19. }
  20. }
  21.  
  22. func (m *Mutex) unlockSlow(new int32) {
    // 解锁一个没有锁定的mutex会报错
  23. if (new+mutexLocked)&mutexLocked == 0 {
  24. throw("sync: unlock of unlocked mutex")
  25. }
    // 判断是否处于饥饿状态,如果不处于饥饿状态
  26. if new&mutexStarving == 0 {
  27. old := new
  28. for {
  29. // If there are no waiters or a goroutine has already
  30. // been woken or grabbed the lock, no need to wake anyone.
  31. // In starvation mode ownership is directly handed off from unlocking
  32. // goroutine to the next waiter. We are not part of this chain,
  33. // since we did not observe mutexStarving when we unlocked the mutex above.
  34. // So get off the way.
    // 如果没有等待的goroutine,或者goroutine已经被唤醒或持有了锁,直接返回
  35. if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
  36. return
  37. }
  38. // Grab the right to wake someone.
    // 唤醒等待者,并将唤醒goroutine的唤醒标志位设置为 1
  39. new = (old - 1<<mutexWaiterShift) | mutexWoken
  40. if atomic.CompareAndSwapInt32(&m.state, old, new) {
  41. runtime_Semrelease(&m.sema, false, 1)
  42. return
  43. }
  44. old = m.state
  45. }
  46. } else {
  47. // Starving mode: handoff mutex ownership to the next waiter, and yield
  48. // our time slice so that the next waiter can start to run immediately.
  49. // Note: mutexLocked is not set, the waiter will set it after wakeup.
  50. // But mutex is still considered locked if mutexStarving is set,
  51. // so new coming goroutines won't acquire it.
    // 如果处于饥饿状态,则直接唤醒第一个goroutine
  52. runtime_Semrelease(&m.sema, true, 1)
  53. }
  54. }

  

go源码阅读 - sync/mutex的更多相关文章

  1. go源码阅读 - sync/rwmutex

    相比于Mutex来说,RWMutex锁的粒度更细,使用RWMutex可以并发读,但是不能并发读写,或者写写. 1. sync.RWMutex的结构 type RWMutex struct { // 互 ...

  2. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  3. 10 DelayQueue 延时队列类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 www.cnblogs.com/oloroso/ 本文由乌合 ...

  4. 转-OpenJDK源码阅读导航跟编译

    OpenJDK源码阅读导航 OpenJDK源码阅读导航 博客分类: Virtual Machine HotSpot VM Java OpenJDK openjdk 这是链接帖.主体内容都在各链接中.  ...

  5. Netty源码阅读(一) ServerBootstrap启动

    Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

  6. JDK1.8源码阅读系列之三:Vector

    本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...

  7. boost.asio源码阅读(2) - task_io_service

    1.0 task_io_service 在boost.asio源码阅读(1)中,代码已经查看到task_io_service中. 具体的操作调用void task_io_service::init_t ...

  8. 【 js 基础 】【 源码学习 】backbone 源码阅读(一)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

  9. 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

随机推荐

  1. rest-framework之视图和源码解析

    视图和源码解析 通过使用mixin类编写视图: from rest_framework import mixins from rest_framework import generics class ...

  2. 使用cgroup和tc限制带宽

    cgroup子系统net_cls 可以给 packet 打上 classid 的标签,用于过滤分类,这个classid就是用于标记skb所属的 qdisc class 的.有了这个标签,流量控制器(t ...

  3. Drools 规则引擎应用 看这一篇就够了

    1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 ...... 1.2传统做法 1 ...

  4. idea使用技巧、心得1

    0.安装idea之后的准备 (1) 永久快乐使用:在我的博客搜索安装idea关键词既可 (2) 取消更新: (3) idea 官网的关于idea的使用手册:https://www.jetbrains. ...

  5. SpringAOP--代理

    前言 在Spring或者SpringBoot中,可以通过@Aspect注解和切点表达式等配置切面,实现对某一功能的织入.然而其内部到底是如何实现的呢? 实际上,Spring在启动时为切点方法所在类生成 ...

  6. 什么是ORM思想?常用的基于ORM的框架有哪些?各有什么特点?

    ORM的全称是Object-Relational Mapping,即对象关系映射.ORM思想的提出来源于对象与关系之间相悖的特性.我们很难通过对象的继承与聚合关系来描述数据表中一对一.一对多以及多对多 ...

  7. 什么是 rabbitmq?

    采用 AMQP 高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦

  8. 什么是 OAuth?

    OAuth 代表开放授权协议.这允许通过在 HTTP 服务上启用客户端应用程序(例 如第三方提供商 Facebook,GitHub 等)来访问资源所有者的资源.因此,您可 以在不使用其凭据的情况下与另 ...

  9. 转:C++初始化成员列表

    转载至:https://blog.csdn.net/zlintokyo/article/details/6524185 C++初始化成员列表和新机制初始化表达式列表有几种用法: 1.如果类存在继承关系 ...

  10. Zookeeper 的典型应用场景 ?

    Zookeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员 可以使用它来进行分布式数据的发布和订阅. 通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watch ...