内容简介:要做Goroutine级别的存储,首先是要获取到Goroutine的标识,之前提到过获取routine id的两个库,效率也比较低下,用在性能要求比较苛刻的场景下并不适合。最近看到有个通过go汇编获取goid的方法,获取到goid以后,我们就可以通过map来存储上下文了。
要做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来实现,实现过程无锁,可以放心服用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First Rails
David Griffiths / O'Reilly Media / 2008-12-30 / USD 49.99
Figure its about time that you hop on the Ruby on Rails bandwagon? You've heard that it'll increase your productivity exponentially, and allow you to created full fledged web applications with minimal......一起来看看 《Head First Rails》 这本书的介绍吧!