class1. Mutex 解决资源并发访问

基础概念

临界区概念:一个被共享的资源,可以被并发访问。通过Mutex互斥锁,可以限定临界区只能由一个线程获取。

  1. 根据不同情况,不同适用场景

●共享资源。并发地读写共享资源,会出现数据竞争(data race) 的问题,所以需要
Mutex、RWMutex 这样的并发原语来保护。
●任务编排。需要goroutine按照一定的规律执行,而goroutine之间有相互等待或者依赖
的顺序关系,我们常常使用WaitGroup或者Channel来实现。
●消息传递。信息交流以及不同的goroutine之间的线程安全的数据交流,常常使用
Channel来实现。

  1. Mutex和RWMutex都实现了sync中的lock接口,
3. type Locker interface{4. Lock()5. Unlock()6. }

自旋锁: 指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断锁能否够被成功获取,直到拿到锁才会退出循环。获取锁的线程持续活跃,不挂起(不是通过休眠来使进程阻塞),继续占有cpu

正常模式: 所有goroutine按照FIFO的顺序进行锁获取,被唤醒的goroutine和新请求锁的goroutine同时进行锁获取,通常新请求锁的goroutine更容易获取锁(持续占有cpu),被唤醒的goroutine则不容易获取到锁

饥饿模式: 所有尝试获取锁的goroutine进行等待排队,新请求锁的goroutine不会进行锁获取(禁用自旋),而是加入队列尾部等待获取锁

并发问题测试工具

在编译(compile) 、测试(test) 或者运行(run) Go代码的时候,加上race参数,就有可能发现并发问题。我们可以加上race参数运行,检.测一下是不是有并发问题。如果你go run -race counter.go,就会输出警告信息。
例如:go run -race main.go
在临界资源前加锁,离开临界区的时候释放锁

这里有一-点需要注意: Mutex的零值是还没有goroutine等待的未加锁的状态,所以 你不需要额外的初始化,直接声明变量(如 var mu sync.Mutex)即可。

如果嵌入的struct有多个字段,我们一-般会把Mutex放在要控制的字段上面,然后使用空格把字段分隔开来。即使你不这样做,代码也可以正常编译,只不过,用这种风格去写的话,逻辑会更清晰,也更易于维护。甚至,你还可以把获取锁、释放锁、计数加一的逻辑封装成一个方法, 对外不需要暴露
锁等逻辑:

思考题

如果 Mutex 已经被一个 goroutine 获取了锁,其它等待中的 goroutine 们只能一直等待。那么等这个锁释放后,等待中的goroutine 中哪一个会优先获取 Mutex 呢?

解答:
等待的goroutine们是以FIFO排队的
1)当Mutex处于正常模式时,若此时没有新goroutine与队头goroutine竞争,则队头goroutine获得。若有新goroutine竞争大概率新goroutine获得。
2)当队头goroutine竞争锁失败1ms后,它会将Mutex调整为饥饿模式。进入饥饿模式后,锁的所有权会直接从解锁goroutine移交给队头goroutine,此时新来的goroutine直接放入队尾。
3)当一个goroutine获取锁后,如果发现自己满足下列条件中的任何一个
1.它是队列中最后一个
2.它等待锁的时间少于1ms,则将锁切换回正常模式

新请求锁的goroutine更容易获取锁的原因:
用官方话说就是,新请求锁的 Goroutine具有优势,它正在CPU上执行,而且可能有好几个,所以刚刚唤醒的 Goroutine 有很大可能在锁竞争中失败.

更多可参考大佬文章::Go Mutex 饥饿模式:
另一个大佬总结的:Go_Concurrency学习地址
学习自:极客时间新专栏 《Go并发编程实战课》