情谊提醒:此篇文章大概须要浏览 5分钟45秒,不足之处请多指教,感激你的浏览。 订阅本站
咱们比拟常见的大型项目的设计中都会呈现并发拜访问题,并发就是为了解决数据的准确性,保障同一个临界区的数据只能被一个线程进行操作,日常中应用到的并发场景也是很多的:
- 计数器:计数器后果不精确;
- 秒杀零碎:因为同一时间访问量比拟大,导致的超卖;
- 用户账户异样:同一时间领取导致的账户透支;
- buffer 数据异样:更新 buffer 导致的数据凌乱。
下面都是并发带来的数据准确性的问题,决绝计划就是应用互斥锁,也就是明天并发编程中的所要形容的 Mutex 并发原语。
实现机制
互斥锁 Mutex 就是为了防止并发竞争建设的并发管制机制,其中有个“临界区”的概念。
在并发编程过程中,如果程序中一部分资源或者变量会被并发拜访或者批改,为了防止并发拜访导致数据的不精确,这部分程序须要率先被爱护起来,之后操作,操作完结后去除爱护,这部分被爱护的程序就叫做临界区。
应用互斥锁,限定临界区只能同时由一个线程持有,若是临界区此时被一个线程持有,那么其余线程想进入到这个临界区的时候,就会失败或者期待开释锁,持有此临界区的线程退出,其余线程才有机会取得这个临界区。
go mutex 临界区示意图
Mutex 是 Go 语言中应用最宽泛的同步原语,也称为并发原语,解决的是并发读写共享资源,避免出现数据竞争 data race 问题。
根本应用
互斥锁 Mutex 提供了两个办法 Lock 和 Unlock:进入到临界区应用 Lock 办法加锁,退出临界区应用 Unlock 办法开释锁 ????。
type Locker interface { Lock() Unlock()}func(m *Mutex)Lock()func(m *Mutex)Unlock()
当一个 goroutine 调用 Lock 办法获取到锁后,其余 goroutine 会阻塞在 Lock 的调用上,直到以后获取到锁的 goroutine 开释锁。
接下来是一个计数器的例子,是由 100 个 goroutine 对计数器进行累加操作,最初输入后果:
package mainimport ( "fmt" "sync")func main() { var mu sync.Mutex countNum := 0 // 确认辅助变量是否都执行实现 var wg sync.WaitGroup // wg 增加数目要和 创立的协程数量保持一致 wg.Add(100) for i := 0; i < 100; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { mu.Lock() countNum++ mu.Unlock() } }() } wg.Wait() fmt.Printf("countNum: %d", countNum)}
理论应用
很多时候 Mutex 并不是独自应用的,而是嵌套在 Struct 中应用,作为构造体的一部分,如果嵌入的 struct 有多个字段,咱们个别会把 Mutex 放在要管制的字段下面,而后应用空格把字段分隔开来。
甚至能够把获取锁、开释锁、计数加一的逻辑封装成一个办法。
package mainimport ( "fmt" "sync")// 线程平安的计数器type Counter struct { CounterType int Name string mu sync.Mutex count uint64}// 加一办法func (c *Counter) Incr() { c.mu.Lock() defer c.mu.Unlock() c.count++}// 取数值办法 线程也须要受爱护func (c *Counter) Count() uint64 { c.mu.Lock() defer c.mu.Unlock() return c.count}func main() { // 定义一个计数器 var counter Counter var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { counter.Incr() } }() } wg.Wait() fmt.Printf("%dn", counter.Count())}
思考问题
Q:你曾经晓得,如果 Mutex 曾经被一个 goroutine 获取了锁,其它期待中的 goroutine 们只能始终期待。那么,等这个锁开释后,期待中的 goroutine 中哪一个会优先获取 Mutex 呢?
A:FIFO,先来先服务的策略,Go 的 goroutine 调度中,会保护一个保障 goroutine 运行的队列,当获取到锁的 goroutine 执行完临界区的操作的时候,就会开释锁,在队列中排在第一地位的 goroutine 会拿到锁进行临界区的操作。