并发问题介绍
在实际项目中,并发是非常常见的,尤其今天电商的兴起如购物时的秒杀系统、秒杀出现超卖、计数器、多个 Goroutine 并发更新同一个资源、用户账户出现透支、buffer 中数据混乱等等问题。
这些问题如何去解决?对,用互斥锁,在 Go 语言中,就是 Mutex。
互斥锁实现机制
首先知道什么是临界资源,是指一个被共享的资源或者一个整体的一组共享资源。比如对数据库的访问、对某共享结构的操作、对一个I/O设备的使用、对一个连接池中的连接调用等等。
对临界区限定同时只能有一个线程持有锁,其他线程如果想访问就会失败,或在外等待,直到持有线程退出临界区、其他线程中某个线程才有机会接待持有这个临界区。
Mutex 基本使用方法
Mutex 是 Go 的标准库中的 sync 包中所提供的一个同步原语,这个包还定义了一个 Lock 接口,而 Mutex 就是实现了这个接口的。
通过进入源码,你会发现互斥锁 Mutex 就是提供两个方法 Lock 和 Unlock,当你进入 临界区之前调用 Lock 方法,退出临界区时调用 Unlock方法。
当一个 goroutine 通过调用 Lock 方法获得了这个锁的拥有权后, 其它请求锁的 goroutine 就会阻塞在 Lock 方法的调用上,直到锁被释放并且自己获取到了这个锁的拥有权。
例子:创建了 10 个 goroutine,同时不断地对一个变量(count)进行加 1 操作,每个 goroutine 负责执行 10 万次的加 1 操作,我们期望的最后计数的结果是 1000000 (一百万)。
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=",count)
}
这里使用 sync.WaitGroup 来等待所有的 goroutine 执行完毕后,再输出最终的结果。上面代码运行结果,不管你运气再好,总是没有输出 一百万
。
为什么?
这就是并发问题了,count++ 不是原子操作,它至少包括几个步骤,如读取变量 count 的当前值,对这个值加1,把结果再保存到 count 中,因为不是原子操作,可能就出现并发问题。
Go 提供了一个检测并发访问共享资源是否有问题的工具,使用 race
参数:go run -race main.go
,这样就会打印出警告信息。
这个警告不但会告诉你有并发问题,而且还会告诉你哪个 goroutine 在哪一行对哪个变量有写操作。该工具只能通过真正对实际地址进行读写访问时才能探测,并不能再编译的时候发现 data race 的问题。
运行 go tool compile -race -S mutexTest.go
,重点关注一下 count++ 前后的编译后 的代码:
以上问题,共享资源是 count 变量,临界区是 count++ ,只要在临界区前面获取锁,离开临界区时候释放锁,就可以解决 data race 问题了,代码如下:
func main() {
var count int = 0
var mu sync.Mutex
var wg sync.WaitGroup
wg.Add(10)
for i:=0;i<10;i++{
go func() {
defer wg.Done()
for j:=0;j<100000;j++{
mu.Lock()
count++
mu.Unlock()
}
}()
}
wg.Wait()
fmt.Println("Count=",count)
}
Mutex 其他用法
很多情况下,Mutex 会嵌入到其它 struct 中使用,比如下面的方式:
type Counter struct {
mu sync.Mutex
Count uint64
}
更多资料收录于GitHub:https://github.com/metashops/GoFamily
欢迎去获取。。。
Java推荐