golang 并发锁的陷阱
错误代码示例
package main
import (
"sync"
"strconv"
"fmt"
)
type Node struct {
sync.Mutex
Data map[string]string
}
var Cache []Node;
func main() {
Cache = make([]Node, 2);
Cache[0] = Node{Data : make(map[string]string)}
Cache[1] = Node{Data : make(map[string]string)}
wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go func (index int) {
defer wg.Done()
j := index % 2
node := Cache[j]
node.Lock()
defer node.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赋值的时候都加了锁,但是在golang 1.8 运行的时候会爆出如下错误
fatal error: concurrent map writes
fatal error: concurrent map writes
goroutine 26 [running]:
runtime.throw(0x10b4392, 0x15)
......
为什么加锁了仍然会报cuncurrent map wirtes,这一定是golang 1.8 的bug(开玩笑的……)!
错误原因
主要原因是golang的struct 在赋值的时候是进行浅拷贝,把结构体的成员进行了copy,Node 结构体有两个成员
type Node struct {
sync.Mutex
Data map[string]string
}
我们从slice中把Node拿出来的时候,其实是copy了一份Node,Map是指针类型的,所以多份copy其实是操作一份map,但是sync.Mutex类型是struct,他进行了一次copy
所以在每个协程中取出来的时候,Mutex都进行了一次copy,Lock的时候不是同一份锁,所以会出现并发map写入。
解决方法1
把Node的成员Mutex 改成指针类型,那么在copy的时候,mutex 能保持对同一份进行Lock,代码如下
package main
import (
"fmt"
"strconv"
"sync"
)
type Node struct {
*sync.Mutex
Data map[string]string
}
var Cache []Node
func main() {
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{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
j := index % 2
node := Cache[j]
node.Lock()
defer node.Unlock()
node.Data[strconv.Itoa(index)] = strconv.Itoa(index)
}(i)
}
wg.Wait()
fmt.Println(Cache[0])
}
Mutex 改成指针类型即可保证同一份锁。
解决方法2 Cache中存储Node指针
Cache中如果是Node指针类型,那么index访问的时候,拿出来是指针的副本,指向的仍然是同一份地址,加锁的时候仍然访问的是同一份资源
代码如下
package main
import (
"fmt"
"strconv"
"sync"
)
type Node struct {
sync.Mutex
Data map[string]string
}
var Cache []*Node
func main() {
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{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
j := index % 2
node := Cache[j]
node.Lock()
defer node.Unlock()
node.Data[strconv.Itoa(index)] = strconv.Itoa(index)
}(i)
}
wg.Wait()
fmt.Println(Cache[0])
}
总结
golang 类似于C++,系统提供的赋值都是浅拷贝,如果确认需要对同一份内容进行访问的时候,需要在特定的地方用上指针
golang 并发锁的陷阱的更多相关文章
- Golang - 并发编程
目录 Golang - 并发编程 1. 并行和并发 2. go语言并发优势 3. goroutine是什么 4. 创建goroutine 5. runtime包 6. channel是什么 7. ch ...
- Redis修改数据多线程并发—Redis并发锁
本文版权归博客园和作者本人吴双共同所有 .转载爬虫请注明地址,博客园蜗牛 http://www.cnblogs.com/tdws/p/5712835.html 蜗牛Redis系列文章目录http:// ...
- Redis并发锁控制
为了防止用户在页面上重复点击或者同时发起多次请求,请求处理需要操作redis缓存,这个时候需要对并发边界进行并发锁控制,实现思路: 由于每个页面发起的请求带的token具备唯一性,可以将token作为 ...
- Redis学习笔记~Redis并发锁机制
回到目录 redis客户端驱动有很多,如ServiceStack.Redis,StackExchange.Redis等等,下面我使用ServiceStack.Redis为例,介绍一下在redis驱动中 ...
- ServiceStack.Redis常用操作 - 事务、并发锁_转
一.事务 使用IRedisClient执行事务示例: using (IRedisClient RClient = prcm.GetClient()) { RClient.Add("key&q ...
- ServiceStack.Redis常用操作 - 事务、并发锁
一.事务 使用IRedisClient执行事务示例: using (IRedisClient RClient = prcm.GetClient()) { RClient.Add("key&q ...
- golang并发编程
golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...
- golang 互斥锁和读写锁
golang 互斥锁和读写锁 golang中sync包实现了两种锁Mutex(互斥锁)和RWMutex(读写锁),其中RWMutex是基于Mutex实现的,只读锁的实现使用类似引用计数器的功能. ty ...
- 马蜂窝搜索基于 Golang 并发代理的一次架构升级
搜索业务是马蜂窝流量分发的重要入口.很多用户在使用马蜂窝时,都会有目的性地主动搜索与自己旅行需求相关的各种信息,衣食住行,事无巨细,从而做出最符合需求的旅行决策. 因此在马蜂窝,搜索业务交互的下游模块 ...
随机推荐
- JSP点击计数器
以下内容引用自http://wiki.jikexueyuan.com/project/jsp/hits-counter.html: 一个点击计数器能得知关于网站某个特定页面的访问量.假设人们第一次登陆 ...
- 【SQL Server 学习系列】-- 随机生成日期时间的SQL脚本
DECLARE @dt1 DATETIME,@dt2 DATETIME,@a BIGINT,@b BIGINT SET @dt1='2010-01-01'--开始日期 SET @dt2='2010-0 ...
- Java 代理模式和装饰者模式的区别
装饰模式:以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案:代理模式:给一个对象提供一个代理对象,并有代理对象来控制对原有对象的引用: 装饰模式应该为所装饰的对象增强功能:代理模式对代理的 ...
- 使用NPOI将DataTable生成Excel
听闻npoi 2.0版本支持excel2007格式了,表示期待其表现.不过目前还是使用1.2.5稳重点. 生活中有太多的列表都需要一个导出功能,当然这里的生活指的的程序员的生活.DataTable是从 ...
- 函数绑定 bind
函数拓展-bind bind实现的是:对函数绑定作用域 更改作用域的方法:call,apply,with,eval,bind call 和 apply 的比较 相同点:1.都是在使用时候(使用即执行) ...
- openstack-glance-api.service start request repeated too quickly, refusing to start
问题描写叙述 openstack J版 centos7部署 重新启动服务时起不来,日志也不报错.以glance服务为例,例如以下: # systemctl start openstack-glance ...
- openstack (1)----- NTP 时间同步服务
一.标准时间 1.地球分为东西十二个区域,共计24个时区 2.格林威治作为全球标准时间即(GMT时间),东时区以格林威治时区进行加,而西时区则进行减 3.地球的轨道并非正圆,在加上自传速度逐年递减,因 ...
- VirtualMachineManager
Java Code Examples for com.sun.jdi.VirtualMachineManager https://www.programcreek.com/java-api-examp ...
- qq xxs
qq xxs qq xxs 有很多举个简单例子 : QQ空间发段文章 带上 <script> 提交(document.cookie) 到 自己服务端获取 </script> 在 ...
- Windows10、ARM开发板、VMware虚拟机同时连接Internet
前段时间有人遇到一些网络连接问题,让我帮忙处理,他想让ARM开发板连接外网,可以连接网络数据库,同时保证自己的电脑可以上网. 本来说直接可以连接一个路由器,分配一个内网IP给ARM就可以了,但是当时那 ...