Go Recursive Mutex Lock!
这篇文章源于我回答的一个问题
我猜提问者想问,为什么Go没有Recursive Lock。
然后我就实现了一个。
https:// github.com/MeteorsLiu/m utex/blob/main/recursive.go
点个star吧。
C++ Recursive Mutex Lock
什么叫多重加锁。
我想了很久,能与之对应的是Recursive Mutex Lock。
但准确来说,这叫 可重入互斥锁 。
其目的就是允许多次加锁,而解锁则是等待全部递归解锁后才真正解锁。
它的用处母庸置疑,如同它的名字“Recursive”。
实现
我参考了MSVC的STL库。因为LLVM里面多数都是在调用pthread_mutex_lock,这个函数比较难看懂。
而MSVC里的是比较简洁干净的。
上代码
由于我们不需要重新设计一遍锁,Golang官方库已经有sync.Mutex了,只需要将其封装即可。
所以,STL里面的计时器,我认为是没有必要的。
看核心实现吧
逻辑很简单,需要用到三个东西,第一TLS(Thread Local Storage)里面的ThreadID,第二一个计数器,第三,互斥锁。
Golang实现
实现起来很简单,因为条件都具备了。把代码写出来即可。
很多人不知道的是,Goroutine实际上也是有一个唯一的ID的,这是在runtime的g结构体定义的,叫做goid。
但可能为了安全考虑,Go并没有将这些runtime结构体和函数进行导出。
但是我们的Recursive Mutex是需要用到Goid的。
这里顺便讲讲Goid的获取。但我为了偷懒,没有自己实现这个Goid获取的功能,用了第三方库。我觉得,这种trick的伎俩,一个人实现就好,别人等着吃螃蟹即可。
不过我这既然是技术文章,自然要讲的。
先上文档
https:// github.com/golang/go/bl ob/master/src/runtime/HACKING.md
golang很贴心的写了个关于内部结构说明。
其中
A "G" is simply a goroutine. It's represented by type g. When a goroutine exits, its g object is returned to a pool of free gs and can later be reused for some other goroutine.
getg()
alone returns the currentg
, but when executing on the system or signal stacks, this will return the current M's "g0" or "gsignal", respectively. This is usually not what you want.
G具体是什么呢,就是代表当前Goroutine的一个上下文。
getg就是通过TLS获取到这个上下文指针的一个函数。
它的实现,就是把TLS里的值取了出来,仅此而已。
这个g,是个大结构体,非常大。
在runtime/runtime2.go中定义
https:// cs.opensource.google/go /go/+/master:src/runtime/runtime2.go;l=413
我们都知道,Goroutine是有栈协程。所以它这个大结构体,包含了它的栈空间指针,当前运行状态等等一堆东西。
其中还包括了它的Goid。
它并不起眼。就是一个uint。
也就是说,我们需要一个getg函数,也需要这个结构体。
那么我们能不能实现呢。
很可惜的是,不能。
因为getg实际上是由汇编编写的,不同平台会编译成不同的汇编代码。
传统的go:linkname偷袭导出,是用不了的。
但我们可以抄袭runtime的getg代码,自己实现。
我使用了这位大佬的库,它就是这么干的
https:// github.com/v2pro/plz/tr ee/master/gls
getg问题解决了,还有另一个问题就是,g结构体。
这坑爹玩意也是不导出的。
我们有两种方法:
- 手动计算出goid的偏移量,然后偏移
- 用反射
这位大佬选择了二者结合。
先算出手动偏移作为默认值,再用反射获得当前版本实际偏移量。
然后就能取得goid了。
总结
其实Recursive Mutex就是一个简单互斥锁而已。
用于比较特殊场所。
但是很遗憾的是,这把Recursive Mutex是违反了Go Mutex设计理念的,Go认为一把锁只允许有一个所有者,这样才能确保数据的唯一性。
但是这又不是标准库,它的设计理念不代表我完全支持,我个人是实用主义者,对于许多思想,我并不完全接受,我是需要什么,我就接受什么。
值得庆幸的是,Go并不会阻挡第三方库的开发。
因为之前写过一些回答,总有人觉得我是Go大舔狗,就疯狂攻击我。实际上我并非是Go狂热追随者,我用Go,仅仅是因为我需要。
除了Recursive Mutex,我还写了一个相当诡异的函数:TryUnlock。
你可能会认为,怎么会有这种东西。
答案还是:因为我需要