Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
5431 人已学习
立即订阅
登录后,你可以任选4讲全文学习
推荐试读
换一换
01 | Mutex:如何解决资源并发访问问题?
02 | Mutex:庖丁解牛看实现
05| RWMutex:读写锁的实现原理及避坑指南
课程目录
已完结/共 22 讲
开篇词 (1讲)
开篇词 | 想吃透Go并发编程,你得这样学!
基本并发原语 (11讲)
01 | Mutex:如何解决资源并发访问问题?
02 | Mutex:庖丁解牛看实现
03|Mutex:4种易错场景大盘点
04| Mutex:骇客编程,如何拓展额外功能?
05| RWMutex:读写锁的实现原理及避坑指南
06 | WaitGroup:协同等待,任务编排利器
07 | Cond:条件变量的实现机制及避坑指南
08 | Once:一个简约而不简单的并发原语
09 | map:如何实现线程安全的map类型?
10 | Pool:性能提升大杀器
11 | Context:信息穿透上下文
原子操作 (1讲)
12 | atomic:要保证原子操作,一定要使用这几种方法
Channel (3讲)
13 | Channel:另辟蹊径,解决并发问题
14 | Channel:透过代码看典型的应用模式
15 | 内存模型:Go如何保证并发读写的顺序?
扩展并发原语 (3讲)
16 | Semaphore:一篇文章搞懂信号量
17 | SingleFlight 和 CyclicBarrier:请求合并和循环栅栏该怎么用?
18 | 分组操作:处理一组子任务,该用什么并发原语?
分布式并发原语 (2讲)
19 | 在分布式环境中,Leader选举、互斥锁和读写锁该如何实现?
20 | 在分布式环境中,队列、栅栏和STM该如何实现?
结束语 (1讲)
结束语 | 再聊Go并发编程的价值和精进之路
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

01 | Mutex:如何解决资源并发访问问题?

你好,我是鸟窝。
今天是我们 Go 并发编程实战课的第一讲,我们就直接从解决并发访问这个棘手问题入手。
说起并发访问问题,真是太常见了,比如多个 goroutine 并发更新同一个资源,像计数器;同时更新用户的账户信息;秒杀系统;往同一个 buffer 中并发写入数据等等。如果没有互斥控制,就会出现一些异常情况,比如计数器的计数不准确、用户的账户可能出现透支、秒杀系统出现超卖、buffer 中的数据混乱,等等,后果都很严重。
这些问题怎么解决呢?对,用互斥锁,那在 Go 语言里,就是 Mutex。
这节课,我会带你详细了解互斥锁的实现机制,以及 Go 标准库的互斥锁 Mutex 的基本使用方法。在后面的 3 节课里,我还会讲解 Mutex 的具体实现原理、易错场景和一些拓展用法。
好了,我们先来看看互斥锁的实现机制。

互斥锁的实现机制

互斥锁是并发控制的一个基本手段,是为了避免竞争而建立的一种并发控制机制。在学习它的具体实现原理前,我们要先搞懂一个概念,就是临界区
在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。
可以说,临界区就是一个被共享的资源,或者说是一个整体的一组共享资源,比如对数据库的访问、对某一个共享数据结构的操作、对一个 I/O 设备的使用、对一个连接池中的连接的调用,等等。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/1000字
划线
笔记
复制
01 | Mutex:如何解决资源并发访问问题?
02 | Mutex:庖丁解牛看实现
05| RWMutex:读写锁的实现原理及避坑指南
13 | Channel:另辟蹊径,解决并发问题
14 | Channel:透过代码看典型的应用模式
结束语 | 再聊Go并发编程的价值和精进之路
该试读文章来自付费专栏《Go 并发编程实战课》,如需阅读全部文章,
请购买文章所属专栏
立即购买
登录 后留言

精选留言(59)

  • Alexdown
    等待的goroutine们是以FIFO排队的
    1)当Mutex处于正常模式时,若此时没有新goroutine与队头goroutine竞争,则队头goroutine获得。若有新goroutine竞争大概率新goroutine获得。
    2)当队头goroutine竞争锁失败1ms后,它会将Mutex调整为饥饿模式。进入饥饿模式后,锁的所有权会直接从解锁goroutine移交给队头goroutine,此时新来的goroutine直接放入队尾。

    3)当一个goroutine获取锁后,如果发现自己满足下列条件中的任何一个#1它是队列中最后一个#2它等待锁的时间少于1ms,则将锁切换回正常模式

    以上简略翻译自https://golang.org/src/sync/mutex.go 中注释Mutex fairness.
    2020-10-12
    4
    138
  • 💎A
    看了下作者的发量,果断入手
    2020-10-12
    2
    87
  • 我来也
    课后思考题:
    分享两篇文章吧(我还没看完😂)

    鸟叔的:sync.mutex 源代码分析
    https://colobu.com/2018/12/18/dive-into-sync-mutex/

    golang源码阅读-sync.Mutex
    https://studygolang.com/articles/17017
    2020-10-12
    3
    26
  • Panmax
    原文中关于 race detector 的介绍有两句话前后矛盾,老师可否解释一下:

    前边说:在编译(compile)、测试(test)或者运行(run)Go 代码的时候,加上 race 参数,就有可能发现并发问题。

    后边却又说:因为它的实现方式,只能通过真正对实际地址进行读写访问的时候才能探测,所以它并不能在编译的时候发现 data race 的问题。

    所以结论是 race detector 并不能在编译阶段发现并发问题?那么前边那句是不是就没必要提了,不然容易让大家误会。

    作者回复: 那借这个问题解答一下吧。编译的时候不能发现data race,但是编译的时候可以开启race参数,这样编译后的程序在运行时就可以data race问题了。你看到很仔细,希望这个解答能把这段解释清楚。
    另外,绝对不要把带race参数编译的程序部署到线上。

    2020-10-18
    4
    16
  • pedro
    大家都已经解答了,就不重复了。这里给一些不熟悉 go 需要的同学补充一下,go 语言查看汇编代码命令:
    go tool compile -S file.go
    对于文中 counter 的例子可以过度优化一下,那就是获取计数的 Count 函数其实可以通过读写锁,也就是 RWMutex 来优化一下。

    作者回复: 你居然“剧透”第五讲的内容������

    2020-10-13
    4
    14
  • Remember九离
    这个课程我想一直跟着走,不单单只是看,我想在吸收课程精华的同时,也进行独立的思考,然后输出,总结,加深理解。因此创建了一个仓库,欢迎大家在学习的同时一起做到手中有码,项目地址:https://github.com/wuqinqiang/Go_Concurrency
    2020-10-12
    1
    12
  • Panda
    推荐一个工具 Chronos - A static race detector for the go language
     https://github.com/amit-davidson/Chronos
    2020-10-20
    10
  • Daiver
    go的goroutine 调度中,内部维护了队列,goroutine在抢占锁的时候,会自旋一段时间,如果抢占失败,这个goroutine会被放到一个FIFO队列中,一般来说,锁释放时,会优先唤醒队头的goroutine,即队头的goroutine优先获得Mutex。
    2020-10-12
    11
  • 骁勇善战
    老师,为什么读也要加锁呢?

    作者回复: 说来话长。
    1.mutex保护的临界区。如果读的时候不加锁,可能会造成不一致的后果,比如部分变量被修改了。
    2.如果临界区比较简单,比如一个int64读写,也可能在一些cpu架构下有可见性问题,导致别的goroutine对变量的写读goroutine看不到

    2021-06-13
    2
    7
  • ZY
    有两种情况
    1. 如果当前有协程进入自旋模式,当前协程会成功获取到锁
    2. 如果没有协程进入自选模式,释放锁的协程会释放的信号量会成功唤醒等待队列中的协程,该卸程会成功获取到锁,并且把等待计数器减1.

    老师:在饥饿模式下,信号量唤醒的协程成功获取到锁之后,该Mutex的模式会改变吗?

    作者回复: 进入自旋不一定会获取到锁。
    饥饿模式不一定改变,看文章。只有等待时间小于阈值或者无等待者时才会改变模式

    2020-10-17
    6
  • 一代咩神
    能否解答一下,为什么获取计数器的值也需要加锁?

    作者回复: 否则获取的时候可能不能得到刚增加的值

    2021-01-14
    5
  • chapin
    个人希望🐤 窝大佬可以多分析一些源码。

    作者回复: 明天一讲剖析mutex

    2020-10-13
    4
  • 无名无姓
    请问老师goroutine里面自旋怎么理解

    作者回复: 一直尝试做获取锁,而不是让渡cpu给其他goroutine

    2021-10-10
    2
  • 面向工资编程
    main 函数里面调用的 wg.Wait() 不是已经能够确保后面的读取 fmt.Println(counter.Count()) 操作和写入 counter.Incr() 这两个 goroutine 的前后顺序了么(确定是先 Incr 完成之后再读取)?为啥读取还要加锁?
    2021-09-03
    1
  • geek_arong2048
    // 得到计数器的值,也需要锁保护
    func (c *Counter) Count() uint64 {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
    }
    老师,在这里读取count值时有必要加锁吗?我理解的是这类简单的读取应该不太需要增加锁,当需要进行读取并修改时才需要进行加锁操作,因为"读取"这个动作自身是原子的,而"读取-修改"这个动作是非原子的
    2021-06-17
    1
  • 初学者
    老师好,“多个goroutine并发更新同一个资源”,这里的同一个资源的条件是不是应该是堆栈分析后分配到堆上的变量,因为堆上是线程共享的,如果是栈上的变量的话,因为是线程独有的就不会出现并发更新的问题,望老师解答下

    作者回复: 对

    2021-05-27
    1
  • Albert
    老师,,获取计数器的值 也加锁,解释为可能不能得到刚增加的值。// 使用WaitGroup等待10个goroutine完成 既然已经完了计数的协程。为啥最后打印计数器 还可能不是最终的值啊?

    作者回复: 不清楚你说的那个代码?不是最终的值肯定是对计数值的更新有并发问题

    2021-01-17
    1
  • 愤怒的显卡
    鸟窝大大你好,请教下为什么在协程内部fmt.Println后最后的结果会是正常的,因为fmt是安全的么
    func main() {
    var count = 0

    var wg sync.WaitGroup

    wg.Add(10)

    for i := 0; i < 10; i++ {
    go func() {
    defer wg.Done()
    for j := 0; j < 10000; j++ {
    count++
    fmt.Println(count)
    }
    }()
    }

    wg.Wait()

    fmt.Println(count)
    }
    2020-11-18
    1
    1
  • jeffery
    对于go小白来说怎么快速的进阶!看鸟叔.发型.必定课程干货满满
    2020-10-27
    1
  • Kcaco
    思路清晰,学到很多!!!
    2020-10-26
    1
收起评论
59
返回
顶部