golang 并发锁的陷阱
本文内容纲要:
-错误代码示例
-错误原因
-解决方法1
-解决方法2Cache中存储Node指针
-总结
错误代码示例
packagemain
import(
"sync"
"strconv"
"fmt"
)
typeNodestruct{
sync.Mutex
Datamap[string]string
}
varCache[]Node;
funcmain(){
Cache=make([]Node,2);
Cache[0]=Node{Data:make(map[string]string)}
Cache[1]=Node{Data:make(map[string]string)}
wg:=sync.WaitGroup{}
fori:=0;i<10000;i++{
wg.Add(1)
gofunc(indexint){
deferwg.Done()
j:=index%2
node:=Cache[j]
node.Lock()
defernode.Unlock()
node.Data[strconv.Itoa(index)]=strconv.Itoa(index)
}(i)
}
wg.Wait();
fmt.Println(Cache[0])
}
看上面这块代码逻辑很简单,并发10000个协程对Cache中的Data进行赋值,偶数index
就赋值到第0
个map,奇数就赋值第1
个map,并且map赋值的时候都加了锁,但是在golang1.8运行的时候会爆出如下错误
fatalerror:concurrentmapwrites
fatalerror:concurrentmapwrites
goroutine26[running]:
runtime.throw(0x10b4392,0x15)
......
为什么加锁了仍然会报cuncurrentmapwirtes,这一定是golang1.8的bug(开玩笑的……)!
错误原因
主要原因是golang的struct在赋值的时候是进行浅拷贝,把结构体的成员进行了copy,Node结构体有两个成员
typeNodestruct{
sync.Mutex
Datamap[string]string
}
我们从slice中把Node拿出来的时候,其实是copy了一份Node,Map是指针类型的,所以多份copy其实是操作一份map,但是sync.Mutex类型是struct,他进行了一次copy
所以在每个协程中取出来的时候,Mutex都进行了一次copy,Lock的时候不是同一份锁,所以会出现并发map写入。
解决方法1
把Node的成员Mutex改成指针类型,那么在copy的时候,mutex能保持对同一份进行Lock,代码如下
packagemain
import(
"fmt"
"strconv"
"sync"
)
typeNodestruct{
*sync.Mutex
Datamap[string]string
}
varCache[]Node
funcmain(){
Cache=make([]Node,2)
Cache[0]=Node{Data:make(map[string]string),Mutex:&sync.Mutex{}}
Cache[1]=Node{Data:make(map[string]string),Mutex:&sync.Mutex{}}
wg:=sync.WaitGroup{}
fori:=0;i<10000;i++{
wg.Add(1)
gofunc(indexint){
deferwg.Done()
j:=index%2
node:=Cache[j]
node.Lock()
defernode.Unlock()
node.Data[strconv.Itoa(index)]=strconv.Itoa(index)
}(i)
}
wg.Wait()
fmt.Println(Cache[0])
}
Mutex改成指针类型即可保证同一份锁。
解决方法2Cache中存储Node指针
Cache中如果是Node指针类型,那么index访问的时候,拿出来是指针的副本,指向的仍然是同一份地址,加锁的时候仍然访问的是同一份资源
代码如下
packagemain
import(
"fmt"
"strconv"
"sync"
)
typeNodestruct{
sync.Mutex
Datamap[string]string
}
varCache[]*Node
funcmain(){
Cache=make([]*Node,2)
Cache[0]=&Node{Data:make(map[string]string)}
Cache[1]=&Node{Data:make(map[string]string)}
//fmt.Println(Cache);return;
wg:=sync.WaitGroup{}
fori:=0;i<10000;i++{
wg.Add(1)
gofunc(indexint){
deferwg.Done()
j:=index%2
node:=Cache[j]
node.Lock()
defernode.Unlock()
node.Data[strconv.Itoa(index)]=strconv.Itoa(index)
}(i)
}
wg.Wait()
fmt.Println(Cache[0])
}
总结
golang类似于C++,系统提供的赋值都是浅拷贝,如果确认需要对同一份内容进行访问的时候,需要在特定的地方用上指针
本文内容总结:错误代码示例,错误原因,解决方法1,解决方法2Cache中存储Node指针,总结,
原文链接:https://www.cnblogs.com/lovelyxia/p/6672336.html