本文我们介绍竞争条件,包括如何检测竞争条件以及如何修复该问题。
什么是竞争条件
竞争条件一般意味着数据竞争,如一些资源被多个线程或协程访问。Golong中资源可能为struct、map、变量等,大多数资源如果不显示声明都不是并发或线程安全的。
典型的线程安全示例为,一些数据库连接被创建为全局变量,它能够在多处并发使用。请看下面示例:
import (
"fmt"
"sync"
)
func main() {
fmt.Println("Start")
m := make(map[int]struct{})
wg := &sync.WaitGroup{}
for i := 0; i<500; i++{
wg.Add(1)
go updateMap(wg, m, i)
}
wg.Wait()
fmt.Println(m)
}
func updateMap(wg *sync.WaitGroup, m map[int]struct{}, r int) {
defer wg.Done()
m[r] = struct{}{}
}
上面代码创建了map并传入updateMap函数,在函数中根据键更新map。运行代码输出错误:
fatal error: concurrent map writes
因为map不是线程安全的,这就是竞争条件的一种场景。当然这种错误可能不会出现,这取决于你如何编写应用代码(是否会造成并发访问资源)。
检测竞争条件
上节我们说明了如何可能发生竞争条件,下面介绍在这之前如何检测。Go提供了标识选型,可以在run/build应用程序时进行检测。请看示例:
示例文件名称为race.go ,可以通过下面命令检测竞争条件:
go run -race main.go 或者 go build -race main.go
go run -race main.go
Start
==================
WARNING: DATA RACE
Write at 0x00c000144450 by goroutine 8:
runtime.mapassign_fast64()
E:/dev-tools/go1.17/src/runtime/map_fast64.go:92 +0x0
main.updateMap()
D:/workspace/golang/wordcount/main.go:25 +0x9e
main.main路dwrap路1()
D:/workspace/golang/wordcount/main.go:16 +0x58
Previous write at 0x00c000144450 by goroutine 7:
runtime.mapassign_fast64()
E:/dev-tools/go1.17/src/runtime/map_fast64.go:92 +0x0
main.updateMap()
D:/workspace/golang/wordcount/main.go:25 +0x9e
main.main路dwrap路1()
D:/workspace/golang/wordcount/main.go:16 +0x58
Goroutine 8 (running) created at:
main.main()
D:/workspace/golang/wordcount/main.go:16 +0xc9
Goroutine 7 (finished) created at:
main.main()
D:/workspace/golang/wordcount/main.go:16 +0xc9
==================
fatal error: concurrent map writes
.....
提醒:通过race选项检测竞争条件非常耗时,一般不要对生产环境应用使用在选项,仅在编码阶段使用。
修复竞争条件
下面看如何修复竞争条件问题,Go提供sync包中互斥锁可以实现同步机制进行解决。
首先创建互斥锁: mx := &sync.Mutex{}
, 它提供了两个方法 Lock 和 Unlock ,利用它们可以锁定代码片段,让代码被同步执行。
完整代码:
import (
"fmt"
"sync"
)
func main() {
fmt.Println("Start")
m := make(map[int]struct{})
wg := &sync.WaitGroup{}
mx := &sync.Mutex{}
for i := 0; i<500; i++{
wg.Add(1)
go updateMap(wg, mx, m, i)
}
wg.Wait()
fmt.Println(m)
}
func updateMap(wg *sync.WaitGroup, mx *sync.Mutex, m map[int]struct{}, r int) {
defer wg.Done()
mx.Lock()
defer mx.Unlock()
m[r] = struct{}{}
}
主要修改了updateMap函数,利用Lock和Unlock同步代码。再次运行不会报错,并正常输出结果。
总结
本文学习了什么是竞争条件,如何检测并通过互斥锁进行修复。
本文参考链接:https://blog.csdn.net/neweastsun/article/details/123012412