Mutex系列是根据我对晁岳攀老师的《Go 并发编程实战课》的吸收和理解整理而成,如有偏差,欢迎指正~

目标

本系列除了希望彻底学习和了解 golang 中 sync.Mutex 的原理和使用,更希望借 golang 中 Mutex 的发展和演变,了解并发场景下锁的设计与实现方法以及不同业务场景下的一些特殊考虑。

Mutex 简介

Mutex 是什么

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

Mutex 定义

尽管 Mutex 的实现经历了多次的重大改版,但是因为设计的巧妙,使用上并没有发生任何变化。

package sync // import "sync"type Mutex struct {    state int32    sema  uint32}    A Mutex is a mutual exclusion lock. The zero value for a Mutex is an    unlocked mutex.    A Mutex must not be copied after first use.func (m *Mutex) Lock()func (m *Mutex) Unlock()

从 Mutex 的定义一眼就能看出来如何使用,加锁使用 Lock 函数,解锁使用 Unlock 函数。

其实 package sync 中定义了 Locker 接口:

package sync // import "sync"type Locker interface {        Lock()        Unlock()}    A Locker represents an object that can be locked and unlocked.

Mutex 实现了 Locker 接口。除了互斥锁 Mutex,像之后会介绍的读写锁 RWMutex,也实现了 Locker 接口。

golang 中 Mutex 演变的4个阶段

现在去看 go1.14 中 Mutex 的实现,是比较复杂和精巧的,但是 Mutex 的复杂和精巧不是一蹴而就的。从初版的 Mutex,到现在的 Mutex,大致经过了以下4个阶段的演变:

接下来会通过这4个阶段对应的 Mutex 源码来理解 golang 在互斥锁的设计思路上的逐渐进化的过程。

希望通过这样一个学习,不仅能更好的掌握 Mutex 这个工具,还能学习到如何设计一个兼顾公平和性能的互斥锁。

初版 Mutex 实现

初版 Mutex 的具体实现如下:

   // CAS操作,当时还没有抽象出atomic包    func cas(val *int32, old, new int32) bool    func semacquire(*int32)    func semrelease(*int32)    // 互斥锁的结构,包含两个字段    type Mutex struct {        key  int32 // 锁是否被持有的标识        sema int32 // 信号量专用,用以阻塞/唤醒goroutine    }        // 保证成功在val上增加delta的值    func xadd(val *int32, delta int32) (new int32) {        for {            v := *val            if cas(val, v, v+delta) {                return v + delta            }        }        panic("unreached")    }        // 请求锁    func (m *Mutex) Lock() {        if xadd(&m.key, 1) == 1 { //标识加1,如果等于1,成功获取到锁            return        }        semacquire(&m.sema) // 否则阻塞等待    }        func (m *Mutex) Unlock() {        if xadd(&m.key, -1) == 0 { // 将标识减去1,如果等于0,则没有其它等待者            return        }        semrelease(&m.sema) // 唤醒其它阻塞的goroutine    }


理解代码之前,先简单介绍下 cas、semacquire 和 semrelease。

cas 的全拼是 compare and set 或者 compare and swap。cas 指令实现的功能是将给定的值 old 和内存中的值 *val 比较,如果相等,将 new 赋值给 *val,否则返回失败。

semacquire 和 semrelease 利用信号量 sema 实现了阻塞和唤醒功能。

接下来我们开始分析上面的代码。

初版 Mutex 的定义

首先看 Mutex 的定义。这个初版的定义其实和最新版的定义的区别在 key 这个字段上。初版中,key 的含义比较简单,就是一个标志位,等于0表示锁未被持有,1表示被某个 goroutine 持有,等于 n 表示还有 n-1 个等待者。

加锁

加锁(Lock)的过程首先是给 key 加1。

如果 key 返回1,则表示当前 goroutine 占有了这把锁,其它 goroutine 只能做候选者。

如果 key 返回n(n > 1),这说明当前有其它 gorutine 正在占用这把锁,所以接下来需要通过信号量机制将当前 goroutine 挂起,加到等待队列,进入阻塞状态。

解锁

解锁(Unlock)的过程是给 key 减1。

如果 key 返回0,表示当前没有其它 goroutine 在等待,可以直接返回;如果 key 返回 n (n > 0),说明还有其它 goroutine 在等待,因此需要通过信号量机制将等待队列中的其它 goroutine 唤醒。

初版 Mutex 的问题

初版 Mutex 在实现的时候,有两个问题:1)Unlock 调用无限制;2)goroutine 唤醒机制性能低下。

Unlock 调用无限制问题

Mutex 本身并没有包含当前 goroutine 的任何信息,因此 Unlock 方法能被任意的 goroutine 调用。这样会导致一个问题,如果某个 goroutine 不按套路来,随便调用 Unlock 函数,让标志位 key 清零,那么数据竞争的问题还是会出现。

Mutex 的这个特性一直保留至今。因此使用 Mutex 的时候,一定要遵循 “谁加锁,谁解锁” 的原则。

goroutine 唤醒机制性能低下

初版 Mutex 唤醒 goroutine 的机制是按排队顺序,谁在前面就先唤醒谁。这样看着很公平,但是从性能上看,并不是最优。因为沉睡的 goroutine 唤醒之后,还需要进行上下文的切换,如果把唤醒机会给当前正占用 CPU 时间片的 goroutine,那么高并发的时候,可能会有更好的性能。

这也是下一个版本的 Mutex 重点解决的问题。

结尾

初版的 Mutex 通过 标志位 key 实现了互斥锁的基本功能。在下一个版本的 Mutex 中,我会重点阐述 Mutex 是如何解决 goroutine 唤醒机制性能低下的问题。


1、初版 Mutex 的详细代码见 https://github.com/golang/go/blob/d90e7cbac65c5792ce312ee82fbe03a5dfc98c6f/src/pkg/sync/mutex.go
2、初版 cas 的详细代码见 https://github.com/golang/go/blob/weekly.2011-01-20/src/pkg/sync/asm_amd64.s
3、初版 semacquire 和 semrelease 的详细代码见 https://github.com/golang/go/blob/d90e7cbac65c5792ce312ee82fbe03a5dfc98c6f/src/pkg/runtime/sema.c

golang goroutine实现_golang中的Mutex设计原理详解(一)相关推荐

  1. jQuery中getJSON跨域原理详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp28 jQuery中getJSON跨域原理详解 前几天我再开发一个叫 河蟹工 ...

  2. 手机快充芯片及其技术标准和设计原理详解

    手机快充芯片及其技术标准和设计原理详解 智能手机对于宽带无线通信.图像处理等多方面的需求导致实际耗电呈指数增长.未来5G通信带宽将比4G增加10倍,4K/8K等高清视频技术逐渐应用,CPU.GPU等运 ...

  3. 干货 | OpenCV中KLT光流跟踪原理详解与代码演示

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:opencv学堂 稀疏光流跟踪(KLT)详解 在视频移动 ...

  4. kafka专题:kafka的总控制器Controller、消费者重分配策略等核心设计原理详解

    文章目录 1. Kafka核心总控制器Controller 1.1 核心总控制器Controller的Leader选举 1.2 Partition副本选举Leader机制 2. 消费者消费偏移量off ...

  5. kafka-02-kafka设计原理详解

    一. 消费者消费消息的offset记录机制 每个consumer会定期将自己消费分区的offset提交给kafka内部topic:__consumer_offsets,提交过去的时候,key是cons ...

  6. Java中Unsafe类的原理详解与使用案例

    点击关注公众号,利用碎片时间学习 1 概述 本文基于JDK1.8. Unsafe类位于rt.jar包,Unsafe类提供了硬件级别的原子操作,类中的方法都是native方法,它们使用JNI的方式访问本 ...

  7. LSM树设计原理详解

    代表数据库:nessDB.leveldb.hbase等 核心思想的核心就是放弃部分读能力,换取写入的最大化能力.LSM Tree ,这个概念就是结构化合并树的意思,它的核心思路其实非常简单,就是假定内 ...

  8. optee中spinlock的实现原理详解

    快速链接: .

  9. SAP ABAP报表依赖设计原理详解

    In SAP note 1230076 "Generation of ABAP loads: Tips for the analysis", a tool report RSDEP ...

最新文章

  1. [云炬创业管理笔记]第二章测试2
  2. python 3解释器_python004 Python3 解释器
  3. Docker的镜像基本原理和概念
  4. javascript实现图片放大镜效果
  5. 2018麦考林杂志计算机科学,最新出炉|2018年麦考林杂志加拿大大学排名!
  6. 解决org.hibernate.QueryException illegal attempt to dereference collection 异常错误
  7. 10个Python进行数据分析的小技巧
  8. 开发前奏曲之添加Android SDK平台工具
  9. 如何在JPG或BMP图片上显示输入的订单数据内容,并在报表打印时显示出来,后台数据库是SQL SERVER 2000 ,先谢了.高分!...
  10. 软件需求说明书/ 概要设计说明书/项目开发计划/详细设计说明书(说明要点及要点解释)
  11. Redis 菜鸟教程学习笔记- Redis 配置
  12. Python 的切片语法为什么不会出现索引越界呢?
  13. 有奖推荐|BSRC发布IoT安全专家招募令
  14. 面试必问为什么想做运营?做运营需要具备哪些特质或素质?
  15. Java poi ppt图片置于底层_POI之PPT图片插入简单实例
  16. 2008服务器系统备份工具,服务器2008系统备份
  17. ​smooth-signature​.js: 前端canvas实现H5带笔锋手写签名,支持PC端和移动端使用,无框架限制,Vue、React等均可使用
  18. HDMI接口简介---分辨率 时钟频率 lane速率计算
  19. C语言动态申请内存空间
  20. Apache的Order Allow,Deny 规则

热门文章

  1. mv强制覆盖 shell_生产力工具:shell 与 Bash 脚本
  2. Kaggle比赛(二)House Prices: Advanced Regression Techniques
  3. resin端口错误问题
  4. web前端学习笔记(二)---Django
  5. webstorm编辑器相关
  6. (大数据工程师学习路径)第二步 Vim编辑器----Vim文档编辑
  7. 【HDU】1695 GCD
  8. jboss ds derby
  9. 57 - 算法 -贪心算法 - 区间不相交问题
  10. html尾部代码_3分钟短文:Laravel Form,让你不再写 HTML 的好“库”