go中数组、切片、map是否线程安全?

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

博客主页🏆看看是李XX还是李歘歘 🏆

🌺每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点以及职场小菜鸡的生活。🌺

💗点关注不迷路总有一些📖知识点📖是你想要的💗 

先给出结论在Go中数组、切片和map都是非线程安全的。

前言

官方给的文档说明go中的所有传递都是值传递都是一个副本一个拷贝。在 go 语言中 值类型赋值都是深拷贝 引用类型一般都是浅拷贝。但是实际传递过程中值类型会生成一个副本进行传递引用类型会生成一个指针副本进行传递所以在go中的所有传递都是值传递。

func lcc(a int) {
    fmt.Println(&a)
}
func lyh(a []int) {
    fmt.Println(&a[0])
}

func main() {
    a := 1
    fmt.Println(&a)
    lcc(a)//值类型复制了一个新的值所以是一个新的地址
    b := []int{1}
    fmt.Println(&b[0])
    lyh(b)//引用类型复制了一个指向该地址的指针所以说地址不变
}


深拷贝会拷贝数据两变量存储地址不同拷贝结束互不影响。浅拷贝只会拷贝内存的地址即使拷贝结束还是互相影响

非线程安全原因

map

Go语言中的 map 在并发情况下只读是线程安全的同时读写是线程不安全的。同一个变量在多个goroutine中访问需要保证其安全性

因为map变量为指针类型变量并发写时多个协程同时操作一个内存类似于多线程操作同一个资源会发生竞争关系共享资源会遭到破坏因此golang出于安全的考虑抛出致命错误fatal error: concurrent map writes

解决方案

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)
}

2sync.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
	})
}

非并发安全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()
}

并发安全map

package main

import (
	"fmt"
	"strconv"
	"sync"
)

var m =sync.Map{}

func get(key string) interface{}{
	a,_:=m.Load(key)
	return a
}

func set(key string, value int) {
	m.Store(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.加锁

2.控制并发顺序

切片

指定索引进行读写是支持并发读写索引区的数据的但是索引区的数据在并发时可能会被覆盖的

发生切片动态扩容并发场景下扩容可能会被覆盖。

切片是对数组的抽象其底层就是数组在并发下写数据到相同的索引位会被覆盖并且切片也有自动扩容的功能当切片要进行扩容时就要替换底层的数组在切换底层数组时多个goroutine是同时运行的哪个goroutine先运行是不确定的不论哪个goroutine先写入内存肯定就有一次写入会覆盖之前的写入所以在动态扩容时并发写入数组是不安全的

解决方案

1.加互斥锁
2.使用channel串行化操作
3.使用sync.map代替切片(github上著名的iris框架也曾遇到过切片动态扩容导致webscoket连接数减少的bug最终采用sync.map解决了该问题 采用sync.map解决切片并发安全)

 

参考

(43条消息) 【golang学习笔记】Go语言中参数的传递是值传递还是引用传递_Vivien_oO0的博客-CSDN博客_golang 切片是值传递还是引用传递

(43条消息) Go并发安全sync.Map_JIeJaitt的博客-CSDN博客 

(43条消息) golang-数组,切片,map是否线程安全_golang 切片线程安全_一颗简单的心的博客-CSDN博客 (43条消息) Go语言map使用和并发安全_行走的皮卡丘的博客-CSDN博客_go map并发安全

(43条消息) GoLang之切片并发安全问题_GoGo在努力的博客-CSDN博客_golang 切片线程安全 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: go