Go语言中map使用和并发安全详解

目录
  • 1 map使用
    • 1.1 map定义
    • 1.2 map的使用和概念
    • 1.3 map的容量
    • 1.4 map的使用
      • 1.4.1 map的遍历
      • 1.4.2 map的删除和断言
    • 1.5 map的坑
  • 2 并发安全
    • 2.1 不安全原因
    • 2.2 解决方案
  • 总结

1 map使用

1.1 map定义

map是一种无序的集合,对应的key (索引)会对应一个value(值),所以这个结构也称为关联数组或字典。

map在其他语言中hash、hash table等

var mapname map[keytype]valuetype

  • mapname 为 map 的变量名。
  • keytype 为键类型。
  • valuetype 是键对应的值类型。

1.2 map的使用和概念

map是引用类型,未初始化的map是nil

package main

import "fmt"

func main() {
	var maplist map[string]int
	maplist["one"] = 1
	fmt.Println(maplist)
}
//报错:panic: assignment to entry in nil map
//map需要先初始化内存后使用

正确做法

package main

import "fmt"

func main() {
	var maplist map[string]int
	maplist = map[string]int{"one": 1, "two": 2}
	maplist["three"] = 3
	fmt.Println(maplist)
}
//map[one:1 three:3 two:2]

当然也可以这样子

package main

import "fmt"

func main() {
	maplist := make(map[string]int)//初始化内存了,想赋值就赋值
	maplist["three"] = 3
	fmt.Println(maplist)
}

map必须先初始化内存,后使用,也就是需要make一下,或者直接赋值一个空map

maplist := map[string]int{}
fmt.Println(maplist)

1.3 map的容量

和数组不同的是,map可以根据新增的key-value动态的伸缩,因此不存在固定长度或者最大限制,但是也可以选择初始化容量的值

maplist := make(map[string]float, 100)

出于性能考虑,对于大的map或者快速扩张的map,最好先标明

用切片作为map的值

maplist1 := make(map[int][]int)
maplist2 := make(map[int]*[]int)

golang里的类型使用灵活,也可以任意组合,map里的值可以是struct,也可以是int、string、甚至是切片、数组。

1.4 map的使用

1.4.1 map的遍历

scene := make(map[string]int)

scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960

for k, v := range scene {
    fmt.Println(k, v)
}

1.4.2 map的删除和断言

package main

import "fmt"

func main() {
	maplist := make(map[string]int)

	// 准备map数据
	maplist["LYY"] = 66
	maplist["520"] = 4
	maplist["666"] = 960

	delete(maplist, "666")

	for k, v := range maplist {
		fmt.Println(k, v)
	}
}

1.5 map的坑

package main

import "fmt"

func main() {
	m := map[int]struct{}{
		1: {},
		2: {},
		3: {},
		4: {},
		5: {},
	}

	for k := range m {
		fmt.Println(k)
	}
}
//没有设置v值的时候,map的遍历是随机的,起始遍历是个随机值

执行第一次

执行第二次

注意:map在增加值、删除时需要加互斥锁

2 并发安全

Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

2.1 不安全原因

官网解释:同一个变量在多个goroutine中访问需要保证其安全性

package main
import (
	"fmt"
	"time"
)
var TestMap map[string]string
func init() {
	TestMap = make(map[string]string, 1)
}
func main() {
	for i := 0; i < 1000; i++ {
		go Write("aaa")
		go Read("aaa")
		go Write("bbb")
		go Read("bbb")
	}
	time.Sleep(5 * time.Second)
}
func Read(key string) {
	fmt.Println(TestMap[key])
}
func Write(key string) {
	TestMap[key] = key
}
//报错 fatal error: concurrent map writes

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

2.2 解决方案

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

执行结果

我们称其为并发安全的map。

总结

到此这篇关于Go语言中map使用和并发安全的文章就介绍到这了,更多相关Go语言map并发安全内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 快速解决Golang Map 并发读写安全的问题

    一.错误案例 package main import ( "fmt" "time" ) var TestMap map[string]string func init() { TestMap = make(map[string]string, 1) } func main() { for i := 0; i < 1000; i++ { go Write("aaa") go Read("aaa") go Write(&qu

  • Golang实现对map的并发读写的方法示例

    在Golang多协程的情况下使用全局map时,如果不做线程同步,会出现panic的情况. 为了解决这个问题,通常有两种方式: 第一种是最常见的使用互斥锁或者读写锁的方法: 第二种是比较符合Golang特色的方法,启动单个协程对map进行读写,当其他协程需要读写map时,通过channel向这个协程发送信号即可. 写了一个模拟程序对map中的一项进行读或者写,后台一直运行的协程阻塞的接受读写信号,并对map进行操作,但是读操作的时候没想好怎么返回这个值. 后来想到用传引用的方式,定义结构体,第一个

  • golang 并发安全Map以及分段锁的实现方法

    涉及概念 并发安全Map 分段锁 sync.Map CAS ( Compare And Swap ) 双检查 分断锁 type SimpleCache struct { mu sync.RWMutex items map[interface{}]*simpleItem } 在日常开发中, 上述这种数据结构肯定不少见,因为golang的原生map是非并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加锁. 之前使用过两个本地内存缓存的开源库, gcache, cache2go,其中

  • Go语言中map使用和并发安全详解

    目录 1 map使用 1.1 map定义 1.2 map的使用和概念 1.3 map的容量 1.4 map的使用 1.4.1 map的遍历 1.4.2 map的删除和断言 1.5 map的坑 2 并发安全 2.1 不安全原因 2.2 解决方案 总结 1 map使用 1.1 map定义 map是一种无序的集合,对应的key (索引)会对应一个value(值),所以这个结构也称为关联数组或字典. map在其他语言中hash.hash table等 var mapname map[keytype]va

  • C语言中的const和free用法详解

    注意:C语言中的const和C++中的const是有区别的,而且在使用VS编译测试的时候.如果是C的话,请一定要建立一个后缀为C的文件,不要是CPP的文件.因为,两个编译器会有差别的. 一.C语言中的const比较常见的用法,const做常量 #include<stdio.h> #include<malloc.h> #include<string.h> /* C中的const用法(使用VS测试的时候,要注意建立一个C后缀的文件,因为C的编译器和C++的编译器还是有区别的

  • C语言中qsort函数的用法实例详解

    C语言中qsort函数的用法实例详解 快速排序是一种用的最多的排序算法,在C语言的标准库中也有快速排序的函数,下面说一下详细用法. qsort函数包含在<stdlib.h>中 qsort函数声明如下: void qsort(void * base,size_t nmemb,size_t size ,int(*compar)(const void *,const void *)); 参数说明: base,要排序的数组 nmemb,数组中元素的数目 size,每个数组元素占用的内存空间,可使用si

  • 在C#和Java语言中for和foreach的区别详解

    for循环和foreach循环的区别 首先在这里声明一点,C#和Java这两种语言很相似,尤其是初学的数据类型那一部分,所以这里写的for和foreach的区别在C#和Java中都适用. 我会在下面分别列出两种语言的for和foreach分别循环打印一个数组,大家可以看看区别 话不多说,直接上代码: //c# //先创建一个数组 int[] arr = new int[3] {99, 11, 22}; //利用for循环打印(可以创建一个变量 i;判断这个i是否小于数组的长度;每次循环i自增1)

  • Java 语言中Object 类和System 类详解

    Object是java所有类的基类,是整个类继承结构的顶端,也是最抽象的一个类.大家天天都在使用toString().equals().hashCode().waite().notify().getClass()等方法,或许都没有意识到是Object的方法,也没有去看Object还有哪些方法以及思考为什么这些方法要放到Object中. 一.Java Object类简介-所有类的超类 Object 是 Java 类库中的一个特殊类,也是所有类的父类.也就是说,J ava 允许把任何类型的对象赋给

  • C语言中printf()缓冲问题详解

    前言 缓冲区又称为缓存,它是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区. 为什么要引入缓冲区 比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度. 又比如,

  • C语言中sizeof()与strlen()的区别详解

    前言 sizeof()和strlen()经常会被初学者混淆,但其中有有很大区别: sizeof() 1. sizeof()[操作数所占空间的字节数大小]是一种c中的基本运算符. 可以以类型.指针.数组和函数等作为参数. 头文件类型为unsigned int. 运算值在编译的时候就出结果,所以可以用来定义数组维数. char a[5]="123"; int b=sizeof(a);//b=5 int c=strlen(a);//c=3 sizeof()是一种单目操作符,是用来计算你所使用

  • golang语言中wasm 环境搭建的过程详解

    golang 安装 通过官方地址 下载.MacOS 也可通过 brew 快速安装: $ brew install golang $ go version go version go1.17.2 darwin/arm64 golang 环境测试 新建文件 main.go ,写入: package main import "fmt" func main() { fmt.Println("Hello World!") } 执行 go run main.go ,将输出: $

  • Go语言中io包核心接口示例详解

    目录 前言 Reader Writer Closer Seeker 组合接口 总结 前言 IO 操作是我们在编程中不可避免会遇到的,例如读写文件,Go语言的 io 包中提供了相关的接口,定义了相应的规范,不同的数据类型可以根据规范去实现相应的方法,提供更加丰富的功能. Go 语言提倡小接口 + 接口组合的方式,来扩展程序的行为以及增加程序的灵活性.io代码包恰恰就可以作为这样的一个标杆,它可以成为我们运用这种技巧时的一个参考标准.io包中包含了大量接口,本篇文章我们就先来学习四个核心接口以及对应

  • C语言中static的使用方法实例详解

    目录 前言 一.static修饰变量 1.修饰局部变量 2.修饰全局变量 二.static修饰函数 补充:static的好处是什么? 总结 前言 static关键字不仅可以用来修饰变量,还可以用来修饰函数.在使用static关键字修饰变量时,我们称此变量为静态变量.静态变量的存储方式与全局变量一样,都是静态存储方式.静态变量属于静态存储方式,属于静态存储方式的变量却不一定就是静态变量. 一.static修饰变量 1.修饰局部变量 先看段代码: #include <stdio.h> void t

随机推荐