通过替换map指针的方式来更新map是否线程安全的?

1. 运行环境

系统: mac os 11.5.2
go版本:1.18.1

2. 问题描述?

问题背景:在线上环境中,有一种常见的场景为:服务启动时需要加载一些数据到内存中,并通过定时更新整体数据的方式供服务使用。这种场景下,通常用到的数据结构为map。

问题内容:
stackoverflow.com/questions/347503... 中说应该加锁;在go里没有明确说这样做是线程安全的;但是我无论是线上环境还是下面的示例代码都没有遇到panic。所以我就很疑惑:在go里,整体更新map数据的时候,通过 直接替换map指针的方式而不是加锁 是否是线程安全的?这样整体更新map是否合适

以下为示例代码:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    mapShow := map[int]bool{}
    for i := 0; i < 1000; i++ {
        mapShow[i] = true
    }

    rand.Seed(time.Now().UnixNano())
    go func() {
        for i := 1; ; i++ {
            time.Sleep(time.Millisecond * 500)
            tmp := map[int]bool{}
            for k := i * 100; k < (i+1)*100; k++ {
                tmp[k] = true
            }
            mapShow = tmp
            fmt.Println(i, "data change success")
        }
    }()

    for i := 0; i < 500; i++ {
        go func() {
            for {
                k := rand.Intn(2000)
                if _, ok := mapShow[k]; ok {
                    fmt.Println(k, "success")
                } else {
                    fmt.Println(k, "not found")
                }
            }
        }()
    }

    select {}
}

3. 您期望得到的结果?

期望遇到panic或者数据不一致情况

4. 您实际得到的结果?

通过上述示例代码跑了2G数据没有遇到异常

最佳答案

我的理解是,golang中map并发读是没问题的,并发写会出现panic,你的代码中使用了单个协程赋值替换了原来的map(此处使用指针替换),并没有执行map的写入逻辑,不会出现并发写问题,且如果使用了多个协程执行替换map的操作,也不会出现panic,但会出现数据不一致的情况。参考:多个协程,没有使用任何互斥状态,对同一个int变量进行加1,程序运行没问题,但每次运行结果会不一致。 go.dev/doc/faq#atomic_maps # Why are map operations not defined to be atomic?

1年前 评论
ry34t42rr (楼主) 1年前
讨论数量: 14

我的理解是,golang中map并发读是没问题的,并发写会出现panic,你的代码中使用了单个协程赋值替换了原来的map(此处使用指针替换),并没有执行map的写入逻辑,不会出现并发写问题,且如果使用了多个协程执行替换map的操作,也不会出现panic,但会出现数据不一致的情况。参考:多个协程,没有使用任何互斥状态,对同一个int变量进行加1,程序运行没问题,但每次运行结果会不一致。 go.dev/doc/faq#atomic_maps # Why are map operations not defined to be atomic?

1年前 评论
ry34t42rr (楼主) 1年前

有问题,golang的map底层是名为hmap的结构体,替换要对结构体的字段一个个赋值,这个过程不是原子的,有中间态。可以用atomic.Value进行原子操作

1年前 评论
ry34t42rr (楼主) 1年前
leoliang (作者) 1年前

退后,我要zb了。

cloud.tencent.com/developer/articl...

file

之前我需要配置文件更新, reload gorm 的实例。网上找的这个文章,我测了下没有问题的。指针替换没什么问题

1年前 评论

对同一个map并发读写会panic,楼主这里读写的是不同的map,不会panic golang.design/go-questions/map/del...

1年前 评论
ry34t42rr (楼主) 1年前
Joeyscat (作者) 1年前

file

1年前 评论
rwxe 6个月前
rwxe 6个月前

这是安全的,尽管GO官方不建议这样做。详见我的文章。 原因是map并不是hmap结构体,map只是一个指向hmap指针,对于不超过机器字长的读写寄存器操作,在绝大多数架构上都是原子的,不存在中间态。

另外您说的“数据不一致”到底是什么意思?你有两个map,一个是MapShow,另一个是tmp,在替换前你读到的是MapShow的内容,在替换后,你读到的是tmp的内容,不存在其他可能。

6个月前 评论

这里评论没一个回答对的,楼主的测试也写的稀烂,你既然要测并发读写写那么多多余的东西干嘛?直接开两个goroutine跑不就完事?你看不会不会panic

func main() {
    mp := map[int]int{1: 1}
    go func() {
        for {
            a := mp[1]
            a++
        }
    }()
    go func() {
        for {
            mp = map[int]int{1: 2}
        }
    }()
    time.Sleep(time.Minute)
    return
}
3个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!