Goroutine的上下文存储(续)

元家昕 · · 788 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

要做Goroutine级别的存储,首先是要获取到Goroutine的标识,之前提到过获取routine id的两个库,效率也比较低下,用在性能要求比较苛刻的场景下并不适合。

最近看到有个通过go汇编获取goid的方法,https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-08-goroutine-id.html。原理其实很简单,golang的内部其实是存储了routineid的,放在了runtime2.go文件的g struct中,只不过这个struct是私有变量,不可以通过外部访问。通过go汇编,我们可以绕过go语言层级带来的障碍,直接访问到对象所在的内存。

获取到goid以后,我们就可以通过map来存储上下文了。

新的问题是,golang的map并不是线程安全,并发的读写会产生问题。实现过程中需要考虑多线程的安全问题。原文提供的方案比较简单,就是在读写过程中对map加互斥锁。进一步的优化方案是使用RWMutex,读map的时候加RLock,写操作加互斥锁,这个也是golang官方推荐的方案,https://blog.golang.org/go-maps-in-action

golang的sync包下面,也有一套map的同步方案,sync/map.go,那么这个方法又有什么特点,我们该怎么选择呢?看了源码以后,发现这一套方案主要是通过空间换时间的方法来减少锁的使用,内部通过两个map存储数据,老的数据存放在read map,新数据存放dirty map,如果数据特征是一次写入,多次读出,那么多数的读请求都会落入read map,不需要加锁,从而提升并发性能。

那么,实际过程中,这三种方案的性能表现到底如何呢?我对不同的读写比例和不同的并发程度做了benchmark,详情见下图。直接上结论,在读写比例100:1以下时RWMutex方案有绝对优势,更高的读写比例下,sync.Map的方案具有更高的性能;RWMutex和sync.Map的性能在大部分情况下都比单锁的方案高,并发程度越高,优势越明显。

10次读操作
100次读操作
1000次读操作

回到我们的场景,上下文存储经常用来处理http server请求相关的数据,减少层层传参。这种场景下,读写比例不会非常高,所以一般来说RWMutex方案是最优的选择。

实现代码和bechmark代码见github,https://github.com/JasonYuan/gls.git

另,在看sync.Map的过程中,顺便看了下sync.atomic,顾名思义,这是保证各种数据操作原子性的的库。看到一个有意思的事情,在amd64下,大部分的内置类型实际上都可以保证写入的原子性,但是interface类型是不能保证的,原因是interface的内部存储是个struct,同时保存了类型和地址。这时候可以通过atomic.Value来实现,实现过程无锁,可以放心服用。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:元家昕

查看原文:Goroutine的上下文存储(续)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

788 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传