转载请附原链,搬砖繁忙回复不及时见谅,技术交流请加QQ群:909211071
作为一个GoLang萌新(其实就是并发编程萌新,之前一直在做PHP),对并发下共享资源的竞争了解不多。所以一开始写出了如下代码:
type OriginPriceController struct {
OriginPriceService *origin_price_service.OriginPriceService
}
func (self *OriginPriceController) action() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
self.Data["product"] = self.OriginPriceService.GetOriginPriceProduct(self.C, productId)
}()
go func() {
defer wg.Done()
self.Data["location"] = self.OriginPriceService.GetOriginPriceLocation(self.C, locationId)
}()
wg.Wait()
}
轻松搞定,并行请求数据,相比之前的串行,性能基本上提升一倍。本以为并发编程也不过如此,就起几个goroutine,用WaitGroup同步多个协程间的状态,等待所有协程都执行完即可。
原本以为这样就搞定了,其实代码中隐含着一个致命问题,当多次请求测试时,很快问题就暴露出来了:
fatal error: concurrent map writes
goroutine 164 [running]:
runtime.throw(0x17e99d3, 0x15)
/usr/local/go/src/runtime/panic.go:774 +0x72 fp=0xc000069ee8 sp=0xc000069eb8 pc=0x102f6c2
runtime.mapassign_faststr(0x16fe080, 0xc000574150, 0x17e0ed5, 0x8, 0x0)
/usr/local/go/src/runtime/map_faststr.go:291 +0x3fe fp=0xc000069f50 sp=0xc000069ee8 pc=0x1014d8e
gin-frame/controllers/first_origin_price.(*FirstOriginPriceController).action.func2(0xc00029e550, 0xc00014e180, 0x571)
/Users/why/Desktop/go/gin-frame/controllers/first_origin_price/first_origin_price_controller.go:62 +0xc4 fp=0xc000069fc8 sp=0xc000069f50 pc=0x164e0d4
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1357 +0x1 fp=0xc000069fd0 sp=0xc000069fc8 pc=0x105cd81
created by gin-frame/controllers/first_origin_price.(*FirstOriginPriceController).action
/Users/why/Desktop/go/gin-frame/controllers/first_origin_price/first_origin_price_controller.go:60 +0x156
没错,就是文章题目里提到的:concurrent map writes
原来Map,对于共享变量,资源,并发写会产生竞争, 共享资源遭到破坏,解决办法是拆分成两个局部变量用于接收goroutine,在goroutine执行完之后再将结果存入公共Map,相应代码如下:
var wg sync.WaitGroup
product := make(map[string]interface{})
location := make(map[string]interface{})
wg.Add(2)
go func() {
defer wg.Done()
product = self.OriginPriceService.GetOriginPriceProduct(self.C, productId)
}()
go func() {
defer wg.Done()
location = self.OriginPriceService.GetOriginPriceLocation(self.C, locationId)
}()
wg.Wait()
self.Data["product"] = product
self.Data["location"] = location
还可以使用sync.Map解决,除了使用互斥量以外,还运用了原子操作,可以保证并发写安全,但是有可能出现锁race。