作者简介:C/C++ 、Golang 领域耕耘者,创作者
个人主页:作者主页
专栏地址: 从原理解析go语言
刷题专栏:leetcode专栏
如果感觉博主的文章还不错的话,还请关注➕ 、点赞👍 、收藏🧡三连支持一下博主哦~~~
当我们在多个协程同时只读时,只用互斥锁会很费很多性能,这时就有了读写锁,主要在下面三个情况用到






源码

w : 互斥锁作为写锁
writerSem : 作为写协程队列
readerSem : 作为读协程队列
readerCount: 正值: 正在读的协程 负值: 加了写锁
readerWait : 写锁应该等待读协程个数(还有等待几个读协程释放)
数据结构示意图:





readerWait 个协程释放锁资源,才能被唤醒readerWait 记录了下一个写协程什么时候唤醒

【注】:代码在RWMutex 中Lock函数
初始状态

会加上一个数, 然后把读协程放出来进去读取,之后锁释放







很简单, 直接readerCount 减 1

这里两边都减 1 ,给写协程维护值

直到减为1, 就说明写协程可以释放了

还是上面场景
package main
import (
"fmt"
"sync"
)
type Person struct {
mtx sync.RWMutex
salary int
level int
}
func (p *Person) promote() {
p.mtx.Lock()
p.salary += 1000
p.level += 1
fmt.Println(p.salary)
fmt.Println(p.level)
p.mtx.Unlock()
}
func (p *Person) print(w *sync.WaitGroup) {
p.mtx.RLock()
defer p.mtx.RUnlock()
fmt.Println(p.salary)
fmt.Println(p.level)
w.Done() // 不止适用于主协程
}
func main() {
p := Person{level: 1, salary: 10000}
wg := sync.WaitGroup{}
wg.Add(3) // 有多少任务
// 模拟三个人给小伙p 升职加薪
go p.print(&wg)
go p.print(&wg)
go p.print(&wg)
wg.Wait() // 等不到3个任务执行的Done 就会卡在这里
// 在主协程中用这个防止退出,如果嫌弃太low, 可换为waitgroup
//time.Sleep(time.Second)
}
/**
不加锁
11000
4
13000
12000
4
4
加锁
11000
2
12000
3
13000
4
*/
现在有一个需求: 实际业务中,一个(组)协程需要等待另一组协程完成

如果想要实现等待组,分析得出,需要记录下面3个东西





package main
import (
"fmt"
"sync"
"time"
)
type Person struct {
mtx sync.RWMutex
salary int
level int
}
func (p *Person) promote() {
p.mtx.Lock()
p.salary += 1000
p.level += 1
fmt.Println(p.salary)
fmt.Println(p.level)
p.mtx.Unlock()
}
func (p *Person) print(w *sync.WaitGroup) {
p.mtx.RLock()
defer p.mtx.RUnlock()
fmt.Println(p.salary)
fmt.Println(p.level)
w.Done() // 不止适用于主协程
}
func main() {
p := Person{level: 1, salary: 10000}
once := sync.Once{}
go once.Do(p.promote)
go once.Do(p.promote)
go once.Do(p.promote)
time.Sleep(time.Second) // 不加这行不会打印
}
找一个变量记录一下,从0变成1 就不在做了
做法: 通过CAS 改值,成功就做
优点: 算法非常简单
问题: 多个协程竞争CAS 改值会造成性能问题
func main() {
p := Person{level: 1, salary: 10000}
once := sync.Once{}
o := int32(0)
atomic.CompareAndSwapInt32(&o, 0, 1)
// 然后到实现的函数中判断这个值是否为1
go once.Do(p.promote)
go once.Do(p.promote)
go once.Do(p.promote)
time.Sleep(time.Second)
}



m := sync.Mutex{}
m.Lock()
/
n := m
m.Unlock()
//在下面是锁不上的,n 是复制上面的那个状态
n.Lock()
注意事项:
永远不要拷贝锁, 直接新建一个锁, n := sync.Mutex{}
上面的例子看似简单,但在实际中,可能出现拷贝结构体, 结构体中有锁的情况,如果直接进行
拷贝就会出现上面的情况;
这种情况也不好发现, 例如下面的代码
p := Person{level: 1, salary: 10000}
p1 := p
在程序中可以执行这条命令go vet main.go,如果出现所拷贝情况, 则会报下面错误

【注】: vet 还能检测可能的bug 或者可疑的构造
不好用语言描述,用一个程序来进行表达
代码如下:
package main
import (
"fmt"
"sync"
)
type Person struct {
mtx sync.RWMutex
salary int
level int
}
func (p *Person) promote() {
p.salary += 1000
p.level += 1
fmt.Println(p.salary)
fmt.Println(p.level)
}
func main() {
p := Person{level: 1, salary: 10000}
for i := 0; i < 200; i++ {
go p.promote()
}
}
将上面的代码执行这条命令 go build -race main.go,然后执行生成的文件, 这就是race 竞争检测

这有一个开源的三方工具,地址如下https://github.com/sasha-s/go-deadlock
直接用这个包, 这个包是继承sync.Lock 的,
但是在代码中出现死锁可以提醒
sema 这个东西往往的被作为的等待队列,贯穿整章
(极少自己使用,往往配合其他东西用)
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习
如果此篇博客对你有帮助的话,可以动一动你的小手~~~
👍 点赞,你的认可是我创作的动力!
🧡 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!