Go并发编程-基本并发原语-1.Mutex解决资源并发访问问题
多个goroutine同时更新用户信息,同时修改一个数据
互斥锁实现机制
互斥锁是并发控制的一个基本手段,是为了避免竞争而建立的一种并发控制机制
临界区
在并发编程中,如果程序的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区
同步原语
同步原语有哪些
互斥锁Mutex,读写锁RWMutex,并发编排WaitGroup,条件变量Cond,Channel等
同步原语的适用场景
- 共享资源:并发地读写共享资源,会出现数据竞争的问题,所以需要Mutex,RWMutex这样的并发原语来保护
- 任务编排:需要goroutine按照一定的规律执行,而goroutine之间有相互等待或者依赖的顺序关系,我们查那个用
WaitGroup
或者Channel
实现 - 消息传递: 信息交流以及不同的goroutine之间的线程安全的数据交流,常常使用
Channel
实现
Mutex的基本使用方法
当一个goroutine通过调用Lock方法获取了这个锁的拥有权的时候,其它请求锁的goroutine就会阻塞在Lock方法的调用上,直到锁被释放并且自己获取到了这个锁的拥有权
举个例子
package main
import (
"fmt"
"sync"
)
func main() {
var count int = 0
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for j := 0; j < 100000; j++ {
count++
}
}()
}
wg.Wait()
fmt.Println(count)
}
每次计算的值都不一样,而且结果也不是我们想要的100000
这就是因被并发访问修改导致的
我们给它加上锁
package main
import (
"fmt"
"sync"
)
func main() {
var count int = 0
var wg sync.WaitGroup
var mutex sync.Mutex
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
mutex.Lock()
for j := 0; j < 100000; j++ {
count++
}
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println(count)
}
使用场景
很多情况下,Mutex会嵌入到其它struct中使用,通过嵌入字段的方式,可以在struct上直接调用Lock/UnLock方法
如果嵌入的struct有多个字段,我们一般会把Mutex放在要控制的字段上面,然后用空格把字段分隔开
代码如下:
package main
import (
"fmt"
"sync"
)
type Counter struct {
sync.Mutex
Count int
}
func (c *Counter) Increment() {
c.Count++
}
var wg sync.WaitGroup
func main() {
counter := Counter{
Count: 0,
}
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
counter.Lock()
for j := 0; j < 100000; j++ {
counter.Increment()
}
counter.Unlock()
}()
}
wg.Wait()
fmt.Println(counter.Count)
}
race detector 竞态检测
编译器通过探测所有的内存访问,加入代码能监视对这些内存地址的访问,在代码运行的时候,race detector 就能监视到对共享变量的非同步访问,出现race的时候,就会打印出警告信息
在编译,测试或者运行代码的时候,加上race参数,就有可能发现并发问题
go run -race xxx.go