Golang中map的赋值操作是否是原子性的
Golang中map的赋值操作是否是原子性的 以下是示例代码:
var data map[string]string
func main() {
go func() {
v := data["d"]
fmt.Println(v)
}()
go func() {
temp := make(map[string]string)
temp["a"] = "a"
data = temp
}()
}
一个协程读取映射,一个协程修改映射,步骤:data = temp 是原子操作吗?
当数据类型是数组时会发生什么?
你好,@skillian 我将一个 map 用作本地缓存,一个协程会读取它,而另一个协程会定期修改这个 map。如果我获取 map 的地址,然后将这个地址赋值给一个全局的 map 地址指针,这个赋值操作是原子性的吗?换句话说,在 Go 语言中,指针赋值是原子性的吗? 这是一个示例代码:
var data *map[string]string
temp :=make(map[string]string)
data=&temp
更多关于Golang中map的赋值操作是否是原子性的的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
一个协程负责读取,另一个协程会定期修改这个映射。
如果没有互斥锁或其他同步机制,这是不安全的,可能会导致程序崩溃。
在 Go 语言中,指针赋值是原子操作吗?
不是。你必须使用 sync/atomic 包,就像我之前的帖子中所做的那样。需要注意的是,x86 架构确实保证了指针的加载和存储是原子操作,但为了让你的代码能在 ARM 等其他架构上正常工作,你必须使用 sync/atomic 包。
你好,@SmallSmartMouse,
不,map 的赋值操作不是原子的。此外,你用创建的 temp map 重新赋值给全局的 data map 也不是原子的。
如果你的想法是初始化一个 map,然后将其存储到一个将被视为只读的全局变量中,你可以使用 sync/atomic 包和一些指针技巧来实现:https://play.golang.org/p/3UmzbTT0Pcl
如果全局 map 需要保持可变,那么你需要使用互斥锁来保护对其的访问:https://play.golang.org/p/khh7W9NnHNi
不过,我认为这两种解决方案可能都不会被其他 Go 开发者所推崇。你应该使用更高级的抽象来控制对 map 的访问,但在不了解你计划如何使用这个 map 的情况下,我无法提供一个好的示例。
在Golang中,data = temp 这个赋值操作是原子性的,但整个map的并发访问仍然存在竞态条件问题。
1. Map赋值操作的原子性
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
var data map[string]string
func main() {
// 指针赋值是原子性的
temp := make(map[string]string)
temp["key"] = "value"
// 这个赋值操作本身是原子的
// 相当于对指针进行原子写入
data = temp
// 从内存模型角度看,这类似于:
atomic.StorePointer(
(*unsafe.Pointer)(unsafe.Pointer(&data)),
unsafe.Pointer(&temp),
)
}
2. 但并发访问仍然不安全
package main
import (
"fmt"
"time"
)
var data map[string]string
func main() {
// 协程1:读取map
go func() {
for i := 0; i < 1000; i++ {
if data != nil {
_ = data["key"] // 这里可能panic
}
}
}()
// 协程2:写入map
go func() {
temp := make(map[string]string)
temp["key"] = "value"
data = temp // 赋值是原子的,但...
// 修改已赋值的map
data["new"] = "value" // 这里与读取并发,不安全
}()
time.Sleep(time.Second)
}
3. 数组的情况
package main
import (
"fmt"
"time"
)
var arr [1000]int
func main() {
// 协程1:读取数组
go func() {
for i := 0; i < 1000; i++ {
fmt.Println(arr[0]) // 可能读到中间状态
}
}()
// 协程2:修改数组
go func() {
newArr := [1000]int{}
for i := 0; i < 1000; i++ {
newArr[i] = i
}
arr = newArr // 数组赋值是值复制,非原子
}()
time.Sleep(time.Second)
}
4. 正确的并发访问方式
package main
import (
"sync"
"sync/atomic"
)
var (
data atomic.Value
mu sync.RWMutex
dataWithMutex map[string]string
)
// 方法1:使用atomic.Value
func safeWithAtomic() {
// 写入
temp := make(map[string]string)
temp["key"] = "value"
data.Store(temp)
// 读取
m := data.Load().(map[string]string)
_ = m["key"]
}
// 方法2:使用sync.RWMutex
func safeWithMutex() {
// 写入
mu.Lock()
dataWithMutex = make(map[string]string)
dataWithMutex["key"] = "value"
mu.Unlock()
// 读取
mu.RLock()
_ = dataWithMutex["key"]
mu.RUnlock()
}
关键点总结:
- 指针赋值是原子的:
data = temp这个操作本身是原子的 - 但map操作不是原子的:对map的读写操作(如
data["key"])不是原子的 - 数组赋值不是原子的:数组是值类型,赋值会复制整个数组
- 需要同步机制:即使赋值是原子的,也需要mutex或atomic.Value来保证并发安全
竞态检测器会报告问题:
go run -race main.go

