🌺每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点,以及职场小菜鸡的生活。🌺
💗点关注不迷路,总有一些📖知识点📖是你想要的💗
目录
先给出结论:在Go中数组、切片和map都是非线程安全的。
什么是线程(并发)安全?线程(并发)安全是指程序在并发执行或者多个线程同时操作的情况下,执行结果还是正确的。
非线程安全原因
map
mapgoroutine
mapgolangfatal error: concurrent map writes
非并发安全map(普通的map)
package main
import (
"fmt"
"strconv"
"sync"
)
var m = make(map[string]int)
func get(key string) int {
return m[key]
}
func set(key string, value int) {
m[key] = value
}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
set(key, n)
fmt.Printf("k=:%v,v:=%v\n", key, get(key))
wg.Done()
}(i)
}
wg.Wait()
}
解决方案
(1)在写操作的时候增加锁
package main
import (
"fmt"
"sync"
)
func main() {
var lock sync.Mutex
var maplist map[string]int
maplist = map[string]int{"one": 1, "two": 2}
lock.Lock()
maplist["three"] = 3
lock.Unlock()
fmt.Println(maplist)
}
(2)sync.Map包
package main
import (
"fmt"
"sync"
)
func main() {
m := sync.Map{} //或者 var mm sync.Map
m.Store("a", 1)
m.Store("b", 2)
m.Store("c", 3) //插入数据
fmt.Println(m.Load("a")) //读取数据
m.Range(func(key, value interface{}) bool { //遍历
fmt.Println(key, value)
return true
})
}
数组
指定索引进行读写时,数组是支持并发读写索引区的数据的,但是索引区的数据在并发时会被覆盖的;
解决方案
1.加锁
2.控制并发顺序
切片
指定索引进行读写:是支持并发读写索引区的数据的,但是索引区的数据在并发时可能会被覆盖的;
发生切片动态扩容:并发场景下扩容可能会被覆盖。
切片是对数组的抽象,其底层就是数组,在并发下写数据到相同的索引位会被覆盖,并且切片也有自动扩容的功能,当切片要进行扩容时,就要替换底层的数组,在切换底层数组时,多个goroutine是同时运行的,哪个goroutine先运行是不确定的,不论哪个goroutine先写入内存,肯定就有一次写入会覆盖之前的写入,所以在动态扩容时并发写入数组是不安全的;
解决方案
1.加互斥锁
2.使用channel串行化操作
3.使用sync.map代替切片(github上著名的iris框架也曾遇到过切片动态扩容导致webscoket连接数减少的bug,最终采用sync.map解决了该问题, 采用sync.map解决切片并发安全)
Go其他数据类型的并发安全性
数据类型参考:
Go 中所有类型并发赋值的安全性。
(1)由一条机器指令完成赋值的类型并发赋值是安全的,这些类型有:字节型,布尔型、整型、浮点型、字符型、指针、函数。
(2)数组由一个或多个元素组成,大部分情况并发不安全。注意:当位宽不大于 64 位且是 2 的整数次幂(8,16,32,64),那么其并发赋值是安全的。
这篇文章写的很好,感谢博主,但是底层是 struct 的类型的channel是并发安全的,还有待博主回复
参考: