前言 本章进入golang系列学习的第二章,我可能是那种写文章很随性的人,学习到哪里可能就记录到哪里。正巧昨天在业务线上发生了由于map的并发读写导致的程序崩溃,由于项目刚刚上线还没有多少人所以问题不大,进行了紧急修复。
锁的概念
锁是生活中非常常见的工具,无论是我家里门的锁还是我们手机的锁都是只能通过一把钥匙或者一个密码才能解锁(当然万能锁除外😁),那么类比生活中的锁不能得出在计算机系统中锁的概念:只有对应钥匙的人才能获取通过锁在房间的资源(个人理解不喜勿喷)
锁的类型
锁的类型有很多,什么悲观锁,乐观锁,分布式锁。多种多样。我们根据自己的业务情况进行使用就可以了 tips:关于分布式锁的实现可以参考下我的上篇文章
golang锁的使用
golang是通过协程来实现并发请求的,那么在多个协程中同步访问一个资源会出现什么问题呢?
为什么要使用锁
翠花上代码:
func main() {
// 定义一个局部变量num, 我们想获取到1+2+3+...+10的值
num := 0
for i := 1; i <= 10; i++ {
num += i
}
fmt.Printf("the last result = %v", num)
}
翠花上结果:
在单个协程下结果是没有问题的。那如果在多个协程下呢?
num := 0
go func() {
num++
fmt.Printf("this one gorountine num is %v \n", num)
}()
go func() {
num++
fmt.Printf("this two gorountinenum is %v\n", num)
}()
go func() {
num--
fmt.Printf("this three gorountinenum is %v\n", num)
}()
time.Sleep(1 * time.Second)
翠花上结果:
在示例中,我们定义了三个协程来分别对共享变量num进行运算,不考虑并发的情况顺序的对num进行运算那么num的值分别为 1,2,1。但是事实上呢并不是这样
结果如下:
上图的结果很明显的不符合我们的预期,正常来说应该是one,two, three进行输出,但是目前发现并不是输出结果的顺序是随机并且每次的值不同。 那为什么结果会出现不同呢。其实这就是我们所说的并发了。 在实际的生产中并不是我们所说的顺序执行,在同一秒甚至同一毫秒的时间内同一段函数会同时处理成百上千的请求,如果函数涉及到同一内存中的变量的修改,那更要额外注意了。例如博主所在的项目,就是json在将json字符串转为map的时候出现了读写的并发导致了线上服务的panic。(妥妥的事故), 所以在进行变量的操作时,记住考虑并发!!!!!!!
sync包的中mutex的使用
在golang的sync包中,存在了保持并发请求的同步原语,例如 mutex, waitgroup等等
- 使用mutex
翠花上代码
num := 0
go func() {
mu.Lock()
num++
fmt.Printf("this one gorountine num is %v \n", num)
mu.Unlock()
}()
go func() {
mu.Lock()
num++
fmt.Printf("this two gorountinenum is %v\n", num)
mu.Unlock()
}()
go func() {
mu.Lock()
num--
fmt.Printf("this three gorountinenum is %v\n", num)
mu.Unlock()
}()
time.Sleep(1 * time.Second)
翠花上结果:
害:输出结果正常了,充分符合我们的预期。可以看到在代码里我们仅仅对内存共享变量进行加锁和解锁。是不是很简单。
- 注意事项
- 共享变量的临界状态
- 死锁
最后:
** 我的公众号 **