map介绍及问题描述

map主要用来存储kv数据,其底层使用的是开链法去冲突的hashtable,拥有自动扩容机制。使用map最方便的一点是可以O(1)快速查询(目前slice并没有提供查询接口,只能通过自己写算法实现某个元素是否存在)。

map虽然好用,但是可能不适用。

但是map有一个非常致命的坑点,在并发场景下,并发读/写都可能会出现fatal error:concurrent map read and map write的错误,刚开始使用map的时候天真的认为只要不对同一个key进行并发操作就行,但是现实很骨感。测试时并发量很小的时候可能不会存在问题(只是运气好),并发量一大就会有问题。

但是不是所有场景下并发使用map都是不安全的
这是golang的官方文档,上面提到了只要有更新的操作存在,map就是非线程安全的,但是如果使用场景只是并发读,不涉及到写/删操作,那么就是并发安全的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwYcv6EP-1597712982988)(https://yunpan.oa.tencent.com/note/api/file/getImage?fileId=5f33ebf86f0b9316e203d9bc)]

源码分析

定义

map head中flags字段,记录了当前map的一些状态,其中hashWriting就是造成并发读写map报错的“罪魁祸首”。

// A header for a Go map.
type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.// Make sure this stays in sync with the compiler's definition.count     int // # live cells == size of map.  Must be first (used by len() builtin)flags     uint8B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for detailshash0     uint32 // hash seedbuckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growingnevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)extra *mapextra // optional fields
}
 // flagsiterator     = 1 // there may be an iterator using bucketsoldIterator  = 2 // there may be an iterator using oldbucketshashWriting  = 4 // a goroutine is writing to the mapsameSizeGrow = 8 // the current map growth is to a new map of the same size

写入

  • 向map中新增元素最终会调用mapassign函数,在新增操作开始之前就会检验flagshashWriting位是否为1,为1则会报错。
  • 检验通过后会将该位置为1,标记当前正在写入。
  • 写入完成后将该位置为0
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {...if h.flags&hashWriting != 0 {throw("concurrent map writes")}hash := t.hasher(key, uintptr(h.hash0))// Set hashWriting after calling t.hasher, since t.hasher may panic,// in which case we have not actually done a write.h.flags ^= hashWriting...
done:if h.flags&hashWriting == 0 {throw("concurrent map writes")}h.flags &^= hashWriting...

读取

读取数据的过程相对简单,在读取之前判断是否有置位,校验通过则可以进行读操作,读操作时不会进行置位的。
这也是为啥,如果一个map被初始化ok之后,只要不做增删改,并发读报错的。

// mapaccess1 returns a pointer to h[key].  Never returns nil, instead
// it will return a reference to the zero object for the elem type if
// the key is not in the map.
// NOTE: The returned pointer may keep the whole map live, so don't
// hold onto it for very long.
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {...if h.flags&hashWriting != 0 {throw("concurrent map read and map write")}...
}

结论

1.看过源码之后,发现这很像一个读写锁,但是并不会造成任何阻塞,有问题直接throw
2.如果真的有初始化一次之后,一直并发读的场景,可以大胆使用map。

常见解决方案

1.自己加锁读。
2.使用sync.map替代(看过一点原理,写数据时实现了加锁;使用了空间换时间的方式,用两个哈希结构存储Map,有一层缓存,加速读取数据。)
3.使用二维切片替代,将key和index做映射。
#如果有理解不到位或者理解失误的地方欢迎指正~

Golang map 并发读写问题源码分析相关推荐

  1. Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析-[Android取经之路]

    摘要:本节主要来讲解Android10.0 logd.logcat读写日志源码内容 阅读本文大约需要花费20分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Andro ...

  2. 并发-阻塞队列源码分析

    阻塞队列 参考: http://www.cnblogs.com/dolphin0520/p/3932906.html http://endual.iteye.com/blog/1412212 http ...

  3. Golang日志框架lumberjack包源码分析

    github地址:  https://github.com/natefinch/lumberjack 获取源码 go get gopkg.in/natefinch/lumberjack.v2 介绍 l ...

  4. Golang|区块链UTXO集源码分析

    区块链UTXO集源码分析 资源 go实现区块链 前提 在未实现UTXO集之前,假设系统需要查询某个钱包地址的余额,系统需要遍历区块链的所有区块,当区块链非常长时,这种做法的成本太高了. UTXO集是未 ...

  5. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  6. Java并发编程 ReentrantLock 源码分析

    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...

  7. java futuretask 源码_java并发编程——FutureTask源码分析

    FutureTask的简单示例: FutureTask的应用场景,如果在当前线程中需要执行比较耗时的操作,但又不想阻塞当前线程时,可以把这些作业交给FutureTask,另开一个线程在后台完成,当当前 ...

  8. MapReduce中map并行度优化及源码分析

    mapTask并行度的决定机制 一个job的map阶段并行度由客户端在提交job时决定,而客户端对map阶段并行度的规划的基本逻辑为:将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分 ...

  9. golang学习之negroni/gizp源码分析

    在 Go 语言里,Negroni 是一个很地道的 Web 中间件,它是一个具备微型.非嵌入式.鼓励使用原生 net/http 库特征的中间件.利用它地Use功能,我们可以很简单地自定义中间件并使用.其 ...

  10. java object monitor_Java精通并发-通过openjdk源码分析ObjectMonitor底层实现

    在我们分析synchronized关键字底层信息时,其中谈到了Monitor对象,它是由C++来实现的,那,到底它长啥样呢?我们在编写同步代码时完全木有看到该对象的存在,所以这次打算真正来瞅一下它的真 ...

最新文章

  1. linux内核中send与recv函数详解
  2. 阿里笔试题—战报交流
  3. Qt数据库操作(三) -- 使用SQL模型类
  4. 【mac开发.NET】No installed provisioning profiles match the installed iOS signing identities
  5. HiccDS共享音乐列表
  6. 计算机科学与技术及应用,计算机科学与技术的应用及发展趋向
  7. [Xcode 实际操作]七、文件与数据-(17)解析JSON文档
  8. (原)PyTorch中使用指定的GPU
  9. 02:陶陶摘苹果【一维数组】
  10. MySQL用sql复制表数据到新表的方法
  11. 关于/r与/n以及 /r/n 的区别总结
  12. 如何提高自己的象棋水平及象棋开局的五种忌讳
  13. Python程序猿必备的几款软件
  14. win10网络计算机删除,手把手教你彻底删除win10系统自带的微软拼音输入法-网络教程与技术 -亦是美网络...
  15. 2008年金融危机的背后原因以及感悟
  16. 观后感|当幸福来敲门 The Pursuit of Happyness
  17. 【渝粤教育】广东开放大学 中国文化与中国文学 形成性考核 (46)
  18. 计算机组成原理课程笔记
  19. form 表单提交后,使页面不跳转
  20. 超强 Python 数据可视化库,一文全解析

热门文章

  1. java.util.Scanner包的使用
  2. 案例推荐《微博:随时随地迎战大流量》
  3. 【记录】【0】好的博客,待整理
  4. 转换到coff期间_“fatal error lnk1123 转换到coff期间失败”的解决方法
  5. 梦参老和尚:糊涂人念〈大悲咒〉往生的故事
  6. ps抠图怎么放大图片_ps中在使用抠图工具时如何用快捷键移动放大的原始图片?...
  7. android 时钟动态图标,安卓 8.1 Launcher3实现动态指针时钟功能
  8. Android安卓 自定义mapbox地图比例尺
  9. 关于PPT母版的含义和使用方法
  10. CS231n 课程(笔记内容 by Aries.Y)