搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > bingge > (190122)map的线程安全问题

(190122)map的线程安全问题

bingge 2019-02-11
举报


  • 前言

  • 1、单纯的map并发下的表现

  • 2、解决map的线程安全问题之手动加锁

  • 3、go1.9+ 中sync.Map 的新解决方案

    • 3.1 syncMap 方法

    • 3.2 替代上面加锁的方案

  • 总结


前言

线程安全就是说多线程访问同一代码,不会产生不确定的结果

上篇文章谈到基本数据类型map的时候,提到它是个线程不安全的,但如果我们希望做到线程安全,是不是就没办法了吗?

本文就进入讨论map并发下安全的解决方案

1、单纯的map并发下的表现

直接举例说明问题,多线程作加1操作,看看最后的值是否等于预期

func main() {	
   type userID int    var userScore = map[userID]int{10: 0}    wg := new(sync.WaitGroup)
   for i := 0; i < 100; i++ {        wg.Add(1)
       go func() {    userScore[10]++    wg.Done() }()    }    wg.Wait()    fmt.Println(userScore) }

并发数量少的情况下,能正常运行,输出能和预期达到一致

并发到100的情况下,编译直接报错了,fatal error: concurrent map writes,编译器直接让你这代码没法玩

小打小闹的时候,要啥并发;真要大闹呢,咋办?接着看

2、解决map的线程安全问题之手动加锁

既然map并发写会出问题,我们就对map加个锁,一个个的写

type userID int
// 重新定义结构体
type userScore struct {    sync.RWMutex    socre map[userID]int
}
// map的操作通过实现的函数执行,内部加锁读写
func (us *userScore) increase(id userID, num int) {    us.Lock()    us.socre[id] += num    us.Unlock() }
func main() {
   var us = &userScore{socre: map[userID]int{}}    wg := new(sync.WaitGroup)
   for i := 0; i < 100; i++ {        wg.Add(1)
       go func() {    us.increase(10, 1)    wg.Done() }()    }    fmt.Println(us.socre) }// map[10:100] 符合预期

实现主要依靠了读写锁。map封装了锁,通过结构体的方法实现对map的读写,避免了写的竞争问题

但有一个问题,在并发写量大的情况下,多个线程会争一个锁,效率会低下,有啥好的办法吗?继续往下看

3、go1.9+ 中sync.Map 的新解决方案

新版本中,引入了 syncMap,不用手动加锁了,同时效率大大提高

实现这个高科技的活,用熟了自己看吧

3.1 syncMap 方法

1func (*Map) Delete  // 删除一个key
2func (*Map) Load    // 获取一个key的值
3func (*Map) LoadOrStore // 存在就返回值;不存在,先存再返回
4func (*Map) Range // 遍历map的for-range 替代解决方案
5func (*Map) Store // 保存一个值(kv对)

3.2 替代上面加锁的方案

首先说明下,替换并发下的先读后写是可不能的,能解决并发写的问题

    // 并发下写正常使用,但是最后值是哪个,就看最后谁写了
    var userScore sync.Map
    wg := new(sync.WaitGroup)	
   for i := 0; i < 100; i++ { wg.Add(1)
       go func(i int) {
           // 写个值    userScore.LoadOrStore("xiaoming", i)    wg.Done() }(i)     }     wg.Wait()     fmt.Println(userScore.Load("xiaoming"))

再看此种模式下的先读后写情况,两个操作分开的执行,存在线程间的交叉,不确定性发生了

func main() {	// 声明类型,自动初始化结构    var userScore sync.Map	// 初始化xiaoming的值
    userScore.Store("xiaoming", 0)
    wg := new(sync.WaitGroup)	
   for i := 0; i < 100; i++ { wg.Add(1)
       go func() {
           // 写个值    old, _ := userScore.Load("xiaoming")
           //interface 转为int类型            nw, _ := old.(int)
           // 原值+1,重新写入    userScore.Store("xiaoming", nw+1)            wg.Done() }()    }    wg.Wait()    fmt.Println(userScore.Load("xiaoming")) }

每次执行的情况,是存在不确定性的,读写两个操作并不是原子性的操作,这样情况下还是考虑锁的方案

总结

基本数据类型map 并发写线程不安全,多个写同时进行会报错

新的引进sync.Map 解决了并发写的问题,

如果map操作的情况下先读后写的情况,还是必须要考虑锁的,保证读写中间没有其他程序的干扰

锁的替换方案还有channel解决方式,实现队列式的访问方式


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《(190122)map的线程安全问题》的版权归原作者「bingge」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注bingge微信公众号

bingge微信公众号:gh_0e88014ac1ee

bingge

手机扫描上方二维码即可关注bingge微信公众号

bingge最新文章

精品公众号随机推荐

举报