01-Mutex:如何解决资源并发访问问题 互斥锁的实现机制 临界区 在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。
临界区就是一个被共享的资源,或者说是一个整体的一组共享资源,比如对数据库的访问、对某一个共享数据结构的操作、对一个 I/O 设备的使用、对一个连接池中的连接的调用,等等。
多个线程同步访问临界区,就会造成访问或操作错误。
使用互斥锁,限定临界区只能为一个线程持有。
Mutex是使用最广泛的同步原语,也叫并发原语
同步原语的使用场景:
共享资源:数据竞争,常用Mutex、RWMutex
任务编排:goroutine按照一定规律执行,常用WaitGroup、Channel
消息传递:不同的goroutine之间线程安全交流,常用Channel
Mutex的基本使用方法 Locker接口 1 2 3 4 type Locker interface { Lock() Unlock() }
互斥锁Mutex提供两个方法Lock和Unlock:进入临界区前调用Lock,退出临界区调用Unlock
1 2 func (m *Mutex) Lock()func (m *Mutex) Unlock()
当一个 goroutine 通过调用 Lock 方法获得了这个锁的拥有权后, 其它请求锁的 goroutine就会阻塞在 Lock 方法的调用上,直到锁被释放并且自己获取到了这个锁的拥有权。
为什么要加锁? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" "sync" )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 < 1000 ; j++ { count++ } }() } wg.Wait() fmt.Println(count) }
执行结果不尽如人意啊!!!
cout++不是一个原子操作!! 好多计数被吞掉了
race detector 编译器通过探测所 有的内存访问,加入代码能监视对这些内存地址的访问(读还是写)。在代码运行的时候,race detector 就能监控到对共享变量的非同步访问,出现 race 的时候,就会打印出警告信息。
1 go run -race example1.go
但是只能通过真正对实际地址进行读写访问的时候才能探测,所以它并不能在编译的时候发现 data race 的问题。
运行go tool compile -race -S example1.go
,可以查看计数器例子的代码
使用方法 临界区是count++,只要在临界区前面获取锁,在离开临界区的时候释放锁,就可以完美解决data race问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "sync" )func main () { var mu sync.Mutex var count = 0 var wg sync.WaitGroup wg.Add(10 ) for i := 0 ; i < 10 ; i++ { go func () { defer wg.Done() mu.Lock() for j := 0 ; j < 1000 ; j++ { count++ } mu.Unlock() }() } wg.Wait() fmt.Println(count) }
其他用法:
嵌入字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "sync" )type Counter1 struct { sync.Mutex count uint64 }func main () { var counter Counter1 var wg sync.WaitGroup wg.Add(10 ) for i := 0 ; i < 10 ; i++ { go func () { defer wg.Done() for j := 0 ; j < 1000 ; j++ { counter.Lock() counter.count++ counter.Unlock() } }() } wg.Wait() fmt.Println(counter.count) }
将获取锁、释放锁、计数加一的逻辑封装成一个方法,不对外暴露锁逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "fmt" "sync" )func main () { var counter Counter2 var wg sync.WaitGroup wg.Add(10 ) for i := 0 ; i < 10 ; i++ { go func () { defer wg.Done() for j := 0 ; j < 1000 ; j++ { counter.Incr() } }() } wg.Wait() fmt.Println(counter.Count()) }type Counter2 struct { CounterType int Name string mu sync.Mutex count uint64 }func (counter *Counter2) Incr() { counter.mu.Lock() counter.count++ counter.mu.Unlock() }func (counter *Counter2) Count() uint64 { counter.mu.Lock() defer counter.mu.Unlock() return counter.count }