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

你好,我是鸟窝。
今天是我们 Go 并发编程实战课的第一讲,我们就直接从解决并发访问这个棘手问题入手。
说起并发访问问题,真是太常见了,比如多个 goroutine 并发更新同一个资源,像计数器;同时更新用户的账户信息;秒杀系统;往同一个 buffer 中并发写入数据等等。如果没有互斥控制,就会出现一些异常情况,比如计数器的计数不准确、用户的账户可能出现透支、秒杀系统出现超卖、buffer 中的数据混乱,等等,后果都很严重。
这些问题怎么解决呢?对,用互斥锁,那在 Go 语言里,就是 Mutex。
这节课,我会带你详细了解互斥锁的实现机制,以及 Go 标准库的互斥锁 Mutex 的基本使用方法。在后面的 3 节课里,我还会讲解 Mutex 的具体实现原理、易错场景和一些拓展用法。
好了,我们先来看看互斥锁的实现机制。
互斥锁的实现机制
互斥锁是并发控制的一个基本手段,是为了避免竞争而建立的一种并发控制机制。在学习它的具体实现原理前,我们要先搞懂一个概念,就是临界区。
在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。
可以说,临界区就是一个被共享的资源,或者说是一个整体的一组共享资源,比如对数据库的访问、对某一个共享数据结构的操作、对一个 I/O 设备的使用、对一个连接池中的连接的调用,等等。
公开
同步至部落
取消
完成
0/2000
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》,新⼈⾸单¥59
《Go 并发编程实战课》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(65)
- 最新
- 精选
- Panmax原文中关于 race detector 的介绍有两句话前后矛盾,老师可否解释一下: 前边说:在编译(compile)、测试(test)或者运行(run)Go 代码的时候,加上 race 参数,就有可能发现并发问题。 后边却又说:因为它的实现方式,只能通过真正对实际地址进行读写访问的时候才能探测,所以它并不能在编译的时候发现 data race 的问题。 所以结论是 race detector 并不能在编译阶段发现并发问题?那么前边那句是不是就没必要提了,不然容易让大家误会。
作者回复: 那借这个问题解答一下吧。编译的时候不能发现data race,但是编译的时候可以开启race参数,这样编译后的程序在运行时就可以data race问题了。你看到很仔细,希望这个解答能把这段解释清楚。 另外,绝对不要把带race参数编译的程序部署到线上。
424 - pedro大家都已经解答了,就不重复了。这里给一些不熟悉 go 需要的同学补充一下,go 语言查看汇编代码命令: go tool compile -S file.go 对于文中 counter 的例子可以过度优化一下,那就是获取计数的 Count 函数其实可以通过读写锁,也就是 RWMutex 来优化一下。
作者回复: 你居然“剧透”第五讲的内容������
419 - 骁勇善战老师,为什么读也要加锁呢?
作者回复: 说来话长。 1.mutex保护的临界区。如果读的时候不加锁,可能会造成不一致的后果,比如部分变量被修改了。 2.如果临界区比较简单,比如一个int64读写,也可能在一些cpu架构下有可见性问题,导致别的goroutine对变量的写读goroutine看不到
211 - ZY有两种情况 1. 如果当前有协程进入自旋模式,当前协程会成功获取到锁 2. 如果没有协程进入自选模式,释放锁的协程会释放的信号量会成功唤醒等待队列中的协程,该卸程会成功获取到锁,并且把等待计数器减1. 老师:在饥饿模式下,信号量唤醒的协程成功获取到锁之后,该Mutex的模式会改变吗?
作者回复: 进入自旋不一定会获取到锁。 饥饿模式不一定改变,看文章。只有等待时间小于阈值或者无等待者时才会改变模式
7 - 一代咩神能否解答一下,为什么获取计数器的值也需要加锁?
作者回复: 否则获取的时候可能不能得到刚增加的值
5 - chapin个人希望🐤 窝大佬可以多分析一些源码。
作者回复: 明天一讲剖析mutex
4 - 无名无姓请问老师goroutine里面自旋怎么理解
作者回复: 一直尝试做获取锁,而不是让渡cpu给其他goroutine
3 - 初学者老师好,“多个goroutine并发更新同一个资源”,这里的同一个资源的条件是不是应该是堆栈分析后分配到堆上的变量,因为堆上是线程共享的,如果是栈上的变量的话,因为是线程独有的就不会出现并发更新的问题,望老师解答下
作者回复: 对
3 - Singin in the Rain在Go 1.20之后的版本,执行命令『go tool compile -race -S counter.go』会报如下的错误: could not import fmt (file not found) could not import sync (file not found) 导致错误的原因是:在Go 1.20之前,标准库被安装到$GOROOT/pkg/$GOOS_$GOARCH。从Go 1.20开始,标准库被构建和缓存但没有安装。可以通过设置GODEBUG=installgoroot=all,然后编译重新安装Go运行环境,恢复$GOROOT/pkg/$GOOS_$GOARCH的使用,但是改动太大。可以通过如下命令解决这个问题: 不带race参数:go build -gcflags=-S counter.go 1> normal.txt 2>&1 带race参数:go build -race -gcflags=-S counter.go 1> race.txt 2>&1 参考链接: https://github.com/golang/go/issues/58629 https://pkg.go.dev/cmd/go https://go.dev/doc/asm
作者回复: 👍🏻
归属地:湖北1 - 党用了一个内存库 github.com/boltdb/bolt,在用-race 运行的时候,里边一个函数的时候报错 panic类型的,fatal error: checkptr: converted pointer straddles multiple allocations 关键的两行报错 D:/GO/src/runtime/checkptr.go:20 +0xc9 fp=0xc00029f9e8 sp=0xc00029f9b8 p c=0x554b89 github.com/boltdb/bolt.(*freelist).write(0xc0004e5290, 0xc000508000, 0xc00050800 0, 0x0) 百度不出来所以然 老师这东西为啥报错啊 不带race运行好好的
作者回复: 看看是不是你数据有并发的问题?如果确定不是,那就是bolt的bug了,不过可能性比较小
1
收起评论