快速解决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("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

二、问题分析

网上关于 golang 编程中 map 并发读写相关的资料很多,但总是都说成 并发读写 造成上面的错误,到底是 并发读 还是 并发写 造成的,这个很多资料都没有说明。

我们把上面的案例分别在循环中注释 Read 和 Write 函数的调用,分别测试 并发读 和 并发写;

循环次数分别测试了 100、1 w、100 w 次,并发读操作绝对不会报上面的错,而并发写基本都会报错。

因此,这个错误主要原因是:map 并发写。

三、问题原因

为什么 map 并发写会导致这个错误? 网络上的相关文章也大都有说明。

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

四、解决方案

网上各路资料解决方案较多,主要思路是通过加锁保证每个协程同步操作内存。

github 上找到一个 concurrentMap 包,案例代码修改如下:

package main
import (
 "fmt"
 cmap "github.com/orcaman/concurrent-map"
 "time"
)
var TestMap cmap.ConcurrentMap
func init() {
 TestMap = cmap.New()
}
func main() {
 for i := 0; i < 100; i++ {
 go Write("aaa", "111")
 go Read("aaa")
 go Write("bbb", "222")
 go Read("bbb")
 }
 time.Sleep(5 * time.Second)
}
func Read(key string) {
 if v, ok := TestMap.Get(key); ok {
 fmt.Printf("键值为 %s 的值为:%s", key, v)
 } else {
 fmt.Printf("键值不存在")
 }
}
func Write(key string, value string) {
 TestMap.Set(key, value)
}

五、思考总结

因为我是以 PHP 打开的编程世界,PHP 语言只有单线程,且不涉及指针操作,变量类型也是弱变量,以 PHP 编程思维刚开始接触 Golang 时还比较容易上手,但越往后,语言的特性区别就体现得越来越明显,思维转变就越来越大,对我来说是打开了一个新世界。

像本文出现的错误案例,也是因为自己没有多线程编程的思维基础,导致对这种问题不敏感,还是花了蛮多时间理解的。希望对和我有相似学习路线的朋友提供到一些帮助。

补充:Golang Map并发处理机制(sync.Map)

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

示例:

package main
import (
 "fmt"
)
var m = make(map[int]int)
func main() {
 //写入操作
 i:=0
 go func() {
 for{
 i++
 m[1]=i
 }

 }()
 //读操作
 go func() {
 for{
 fmt.Println(m[1])
 }

 }()
 //无限循环,让并发程序在后台运行
 for {
 ;
 }
}

从以上示例可以看出,不断地对map进行读和写,会出现错误。主要原因是对map进行读和写发生了竞态问题。map内部会对这种并发操作进行检查并提前发现。

如果确实需要对map进行并发读写操作,可以采用加锁机制、channel同步机制,但这样性能并不高。

Go语言在1.9版本中提供了一种效率较高的并发安全的sync.Map。

sync.Map结构如下:

The zero Map is empty and ready for use. A Map must not be copied after first use.
type Map struct {
 mu Mutex
 misses int
}

// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
}

// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {

}
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored.
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
}

// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
} 

// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
//
// Range does not necessarily correspond to any consistent snapshot of the Map's
// contents: no key will be visited more than once, but if the value for any key
// is stored or deleted concurrently, Range may reflect any mapping for that key
// from any point during the Range call.
//
// Range may be O(N) with the number of elements in the map even if f returns
// false after a constant number of calls.
func (m *Map) Range(f func(key, value interface{}) bool) {
}

func (m *Map) missLocked() {

}

func (m *Map) dirtyLocked() {

}

其实,sync.Map内部还是进行了加锁机制,不过进行了一定的优化。

sync.Map使用示例:

package main
import (
 "fmt"
 "sync"
 "time"
)

var m1 sync.Map
func main() {
 i := 0
 go func() {
 for {
 i++
 m1.Store(1, i)
 time.Sleep(1000)
 }
 }()
 go func() {
 for{
 time.Sleep(1000)
 fmt.Println(m1.Load(1))
 }

 }()
 for {
 ;
 }
}

成功运行效果如下:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • 浅谈golang并发操作变量安全的问题

    我就废话不多说了,大家还是直接看代码吧~ package main import ( "fmt" "time" "sync" "sync/atomic" ) func main() { test1() test2() } func test1() { var wg sync.WaitGroup count := 0 t := time.Now() for i := 0 ; i < 50000 ; i++ { wg.Add

  • 解决Golang map range遍历结果不稳定问题

    闲言少叙,本文主要是想介绍一个Golang开发常见的一个问题.然而,此问题对于初学者来说却经常容易陷入坑中. 问题 我在写一段代码时,使用了Golang的map数据结构,目的是想用map缓存计数结果.简单来说map的键也是整型的,且以递增顺序存储.我的最初想法是,在统计结束后,按照map中存储的键有序输出值.可是,当我运行程序时,结果并不是我想要的,而且有一定概率运行结果不同. 问题代码 func sortByBits(arr []int) []int { var bitmap = make(m

  • 浅谈golang类型断言,失败类型断言返回值问题

    失败的类型断言,返回的值为最近断言类型的零值 代码入下: func main() { var data interface{} = "ehoo" if res, ok := data.(int); ok { fmt.Printf("int res:%d\n", res) } else if res, ok := data.(bool); ok { fmt.Printf("bool res:%b\n", res) } else { fmt.Prin

  • golang使用aes库实现加解密操作

    golang实现加密解密的库很多,这里使用的是aes库+base64库来实现. 使用时,需要指定一个私钥,来进行加解密,这里指定是: var aeskey = []byte("321423u9y8d2fwfl") 上代码: package main import ( "fmt" "crypto/cipher" "crypto/aes" "bytes" "encoding/base64"

  • golang执行命令操作 exec.Command

    我就废话不多说了,大家还是直接看代码吧~ cmd := exec.Command("cmd") in := bytes.NewBuffer(nil) cmd.Stdin = in//绑定输入 var out bytes.Buffer cmd.Stdout = &out //绑定输出 go func() { in.WriteString("node E:/design/test.js\n")//写入你的命令,可以有多行,"\n"表示回车 }

  • golang等待触发事件的实例

    我就废话不多说了,大家还是直接看代码吧~ type Wait interface { // Register waits returns a chan that waits on the given ID. // The chan will be triggered when Trigger is called with // the same ID. Register(id uint64) <-chan interface{} // Trigger triggers the waiting c

  • golang移除数组中重复的元素操作

    我就废话不多说了,大家还是直接看代码吧~ 方法一: //这种发放适用于string,int,float等切片,会对切片中的元素进行排序 func SliceRemoveDuplicates(slice []string) []string { sort.Strings(slice) i:= 0 var j int for{ if i >= len(slice)-1 { break } for j = i + 1; j < len(slice) && slice[i] == sl

  • 快速解决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

  • 6行代码快速解决golang TCP粘包问题

    前言 什么是TCP粘包问题以及为什么会产生TCP粘包,本文不加讨论.本文使用golang的bufio.Scanner来实现自定义协议解包. 下面话不多说了,来一起看看详细的介绍吧. 协议数据包定义 本文模拟一个日志服务器,该服务器接收客户端传到的数据包并显示出来 type Package struct { Version [2]byte // 协议版本,暂定V1 Length int16 // 数据部分长度 Timestamp int64 // 时间戳 HostnameLength int16

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

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

  • Go 并发读写 sync.map 详细

    目录 1.sync.Map 优势 2.性能测试 2.1 压测结果 1)写入 2)查找 3)删除 2.3 场景分析 3.sync.Map 剖析 3.1 数据结构 3.2 查找过程 3.3 写入过程 3.4 删除过程 map 的两种目前在业界使用的最多的并发支持的模式分别是: 原生 map + 互斥锁或读写锁 mutex. 标准库 sync.Map(Go1.9及以后). 有了选择,总是有选择困难症的,这两种到底怎么选,谁的性能更加的好?我有一个朋友说 标准库 sync.Map 性能菜的很,不要用.我

  • 快速解决SpringMVC @RequestBody 用map接收请求参数的问题

    一:遇到个跨域调用,因为传个我的参数不定,所以需要通过map来接收参数并进行签名验证等操作 理所当然的写出了下面的代码,但是发现map里并没有获取到传来的key-value值 @RequestMapping(value = "/callback", produces = "text/html;charset=UTF-8") @ResponseBody public String callback(@RequestBody Map<String, String&

  • 快速解决设置Android 23.0以上版本对SD卡的读写权限无效的问题

    对Android的SD卡进行读取权限设置时: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 结果报错: 依然提示我没有权限,于是百度说是版本问题,23.0版本(笔者的版本是25.0)以上的

  • Golang map实践及实现原理解析

    目录 Map实践以及实现原理 使用实例 内存模型 创建map hash函数 key定位和碰撞解决 扩容 元素访问 删除 迭代 Map实践以及实现原理 使用实例内存模型创建maphash函数key定位和碰撞解决扩容元素访问删除迭代核心点: 使用实例 测试的主要目的是对于map,当作为函数传参时候,函数内部的改变会不会透传到外部,以及函数传参内外是不是一个map,也就是传递的是实例还是指针.(golang里面的传参都是值传递). Test Case1:传参为map. func main(){ fmt

  • GoLang切片并发安全解决方案详解

    目录 1.介绍切片并发问题 2.实践检验真理 3.回答切片并发安全问题 4.解决切片并发安全问题方式 5.附 1.介绍切片并发问题 关于切片的,Go语言中的切片原生支持并发吗? 2.实践检验真理 实践是检验真理的唯一标准,所以当我们遇到一个不确定的问题,直接写demo来验证,因为切片的特点,我们可以分多种情况来验证 1.不指定索引,动态扩容并发向切片添加数据 2.指定索引,指定容量并发向切片添加数据 不指定索引,动态扩容并发向切片添加数据 不指定索引,动态扩容并发向切片添加数据: 通过打印数据发

  • Golang Map实现赋值和扩容的示例代码

    golang map 操作,是map 实现中较复杂的逻辑.因为当赋值时,为了减少hash 冲突链的长度过长问题,会做map 的扩容以及数据的迁移.而map 的扩容以及数据的迁移也是关注的重点. 数据结构 首先,我们需要重新学习下map实现的数据结构: type hmap struct { count int flags uint8 B uint8 noverflow uint16 hash0 uint32 buckets unsafe.Pointer oldbuckets unsafe.Poin

随机推荐