golang中的map并发读写问题:golang协程并发使用map的正确姿势

禅与计算机程序设计艺术 禅与计算机程序设计艺术     2022-12-02     697

关键词:

map 不是并发安全的

官方的faq里有说明,考虑到有性能损失,map没有设计成原子操作,在并发读写时会有问题。

Map access is unsafe only when updates are occurring. As long as all goroutines are only reading—looking up elements in the map, including iterating through it using a for range loop—and not changing the map by assigning to elements or doing deletions, it is safe for them to access the map concurrently without synchronization.

查看源码,进一步立即实现机制

const (
  ...
    hashWriting  = 4 // a goroutine is writing to the map
    ...
)

type hmap struct 
    ...
    flags     uint8
    ...

map是检查是否有另外线程修改h.flag来判断,是否有并发问题。

// 在更新map的函数里检查并发写
    if h.flags&hashWriting == 0 
        throw("concurrent map writes")
    
    
// 在读map的函数里检查是否有并发写
    if h.flags&hashWriting != 0 
        throw("concurrent map read and map write")
    

测试并发问题的例子:一个goroutine不停地写,另一个goroutine不停地读

package main

import (
    "fmt"
    "time"
)

func main() 
    c := make(map[string]int)
    go func()  //开一个goroutine写map
        for j := 0; j < 1000000; j++ 
            c[fmt.Sprintf("%d", j)] = j
        
    ()
    go func()  //开一个goroutine读map
        for j := 0; j < 1000000; j++ 
            fmt.Println(c[fmt.Sprintf("%d", j)])
        
    ()
    time.Sleep(time.Second * 20)

立马产生错误

0
fatal error: concurrent map read and map write

goroutine 19 [running]:
runtime.throw(0x10d6ea4, 0x21)
        /usr/local/go/src/runtime/panic.go:774 +0x72 fp=0xc00009aef0 sp=0xc00009aec0 pc=0x10299c2
runtime.mapaccess1_faststr(0x10b41e0, 0xc000066180, 0x116ae71, 0x1, 0x1)
        /usr/local/go/src/runtime/map_faststr.go:21 +0x44f fp=0xc00009af60 sp=0xc00009aef0 pc=0x100ffff
main.main.func2(0xc000066180)

加sync.RWMutex来保护map

This statement declares a counter variable that is an anonymous struct containing a map and an embedded sync.RWMutex.

var counter = struct
    sync.RWMutex
    m map[string]int
m: make(map[string]int)
To read from the counter, take the read lock:

counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
To write to the counter, take the write lock:

counter.Lock()
counter.m["some_key"]++
counter.Unlock()

针对上面有并发问题的测试例子,可以修改成以下代码:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() 
    var c = struct 
        sync.RWMutex
        m map[string]int
    m: make(map[string]int)

    go func()  //开一个goroutine写map
        for j := 0; j < 1000000; j++ 
            c.Lock()
            c.m[fmt.Sprintf("%d", j)] = j
            c.Unlock()
        
    ()
    go func()  //开一个goroutine读map
        for j := 0; j < 1000000; j++ 
            c.RLock()
            fmt.Println(c.m[fmt.Sprintf("%d", j)])
            c.RUnlock()
        
    ()
    time.Sleep(time.Second * 20)


第三方 map 包

第三方包的实现都大同小异,基本上都是使用分离锁来实现并发安全的,具体分离锁来实现并发安全的原理可参考下面的延伸阅读

concurrent-map

m:= cmap.New()
//写
m.Set("foo", "hello world")
m.Set("slice", []int1, 2, 3, 4, 5, 6, 7)
m.Set("int", 1)
//读
m.Get("foo")  
m.Get("slice") 
m.Get("int")  
go-concurrentMap

m := concurrent.NewConcurrentMap()
m.Put("foo", "hello world")
m.Put("slice", []int1, 2, 3, 4, 5, 6, 7)
m.Put("int", 1)
//读
m.Get("foo")  
m.Get("slice") 
m.Get("int") 

sync.Map

sync.Map 是官方出品的并发安全的 map,他在内部使用了大量的原子操作来存取键和值,并使用了 read 和 dirty 二个原生 map 作为存储介质,具体实现流程可阅读相关源码。

参考:https://learnku.com/articles/27691

参考链接

  1. The Go Blog - Go maps in action

  2. Why are map operations not defined to be atomic?

golang语言map的并发和排序

参考技术Agolang语言map的并发和排序golang缺省的map不是threadsafe的,如果存在读写并发的使用场景,必须在外面使用lock机制。包sync里面引入一个安全map;用法:运行结果如下:golang官方说法map并不排序,不按key排序,也不按插入... 查看详情

golang并发读写map安全问题详解

参考技术A下面先写一段测试程序,然后看下运行结果:运行结果:发生了错误,提示:fatalerror:concurrentmapreadandmapwrite,map发生了同时读和写了;但是这个错误并不是每次运行都会出现,就是有的时候会出现,有的时候并不会出... 查看详情

golang中sync.map的实现原理

参考技术A前面,我们讲了map的用法以及原理Golang中map的实现原理,但我们知道,map在并发读写的情况下是不安全。需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在1.9版本中提供了一种效率较高的并发安全的syn... 查看详情

golang百万级高并发实例(代码片段)

前言感谢Handling1MillionRequestsperMinutewithGo这篇文章给予的巨大启发。基础我们使用Go语言,基本上是因为他原生支持的高并发:Goroutine和Channel;Go的并发属于CSP并发模型的一种实现;CSP并发模型的核心概念是:“不要通过共享内存... 查看详情

不得不知道的golang之sync.map解读!

...项目中遇到了需要使用高并发的map的场景,众所周知golang官方的原生map是不支持并发读写的,直接并发的读写很容易触发panic。解决的办法有两个:自己配一把锁(sync.Mutex)& 查看详情

golang入门到项目实战golang并发编程之runtime包

参考技术Aruntime包里面定义了一些协程管理相关的api让出CPU时间片,重新等待安排任务运行结果退出当前协程运行结果运行结果如果修改最大核心数为1,运行结果如下,不会出现交替执行现象 查看详情

day864.csp模型-java并发编程实战(代码片段)

...-Passing)的方式多线程通信,以通信方式共享内存Golang是一门号称从语言层面支持并发的编程语言,支持并发是Golang一个非常重要的特性。在协程,Golang支持协程,协程可以类比Java中的线程,解决并发问题... 查看详情

golang入门:并发(代码片段)

Golang入门(4):并发摘要并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要。Web服务器会一次处理成千上万的请求,这也是并发的必要性之一。Golang的并发控制比起Java来说&... 查看详情

golang中grpc服务器中的并发模型

】golang中grpc服务器中的并发模型【英文标题】:Concurrenymodelingrpcserveringolang【发布时间】:2019-04-1705:20:41【问题描述】:我在golang中创建了一个示例gRPC客户端和服务器(使用了protobufs)。我了解golang中的并发模型。但是,我试... 查看详情

(四十二)golang--管道(代码片段)

假设我们现在有这么一个需求:计算1-200之间各个数的阶乘,并将每个结果保存在mao中,最终显示出来,要求使用goroutime。分析:(1)使用goroutime完成,效率高,但是会出现并发/并行安全问题;(2)不同协程之间如何通信;对... 查看详情

golang并发编程之生产者消费者(代码片段)

golang最吸引人的地方可能就是并发了,无论代码的编写上,还是性能上面,golang都有绝对的优势学习一个语言的并发特性,我喜欢实现一个生产者消费者模型,这个模型非常经典,适用于很多的并发场景,下面我通过这个模型,... 查看详情

[golang]channel通道(代码片段)

说明channel是go当中的一个核心类型,可以看做是管道。并发核心单元可以通过channel进行数据的发送和接收,从而实现通信。在go中,channel是一种数据类型,主要被用来解决协程的同步问题以及协程之间数据共享(数据传递)的问题... 查看详情

白话golang协程池(代码片段)

文章目录1.何为并发2.并发的好处3.Go如何并发4.G-P-M调度模型5.Go程的代价6.协程池的作用7.简易协程池的设计&实现8.开源协程池的使用9.小结参考文献1.何为并发并发指在一段时间内有多个任务(程序,线程,协程等&#... 查看详情

golang从map获取值时的值拷贝问题

参考技术A我们知道golang中,slice,map,channel是引用类型,函数之间传递都是以值拷贝的形式进行的,引用类型经过函数传递,依然是引用类型。在上述例子中,我们从map中想拿出一个值,这个值是一个简单结构体,拿出这个值... 查看详情

golang并发模式(代码片段)

文章目录1.全部返回2.出错及时返回3.最早成功返回4.小结参考文献Go为并发而生。在使用Go编写并发程序时,我们应该熟悉常见的并发模式。虽然业务开发中常用的可能只有那么一两种,但还是有必要了解一下,因为面... 查看详情

golang并发模式(代码片段)

文章目录1.全部返回2.出错及时返回3.最早成功返回4.小结参考文献Go为并发而生。在使用Go编写并发程序时,我们应该熟悉常见的并发模式。虽然业务开发中常用的可能只有那么一两种,但还是有必要了解一下,因为面... 查看详情

Golang 中的嵌套地图

】Golang中的嵌套地图【英文标题】:NestedmapsinGolang【发布时间】:2017-11-0211:44:15【问题描述】:funcmain()vardata=map[string]stringdata["a"]="x"data["b"]="x"data["c"]="x"fmt.Println(data)它运行。funcmain()vardata=map[string][]stringdata["a"]=append(d 查看详情

由浅入深聊聊golang的sync.map(代码片段)

...,其实之前就有写过map相关文章:由浅入深聊聊Golang的map。但是没有详细说明sync.Map是怎么一回事。回想了一下,竟然脑中只剩下“两个map、一个只读一个读写,xxxxx”等,关键词。有印象能扯,但是有点... 查看详情