golang支持tls

前言

golang官方默认是不支持tls(Thread Local Storage)的。支持tls的语言也不多,比如C++和GCC-C(gcc的c语言扩展,用于linux)。go官方拒绝实现tls,而为了解决tls问题,引入了上下文context包(闭包一定程度也是一种上下文)。通过上下文参数传递的方式来传递本地变量。这种方式好处就是显式上下文明确,坏处就是每个函数都要多一个上下文参数,传来传去的。黑客当然不会满足于此,于是乎一个哥们开源了go-tls
https://github.com/huandu/go-tls
代码不复杂,使用也比较简单。参考它的READ即可。

go-tls原理

简单介绍一下实现原理
它为每个协程定义了全局唯一的递增id,以及定义了一个全局的map用于存储每个协程的数据。map的key是协程的g结构地址,如此保证了数据是局部于协程的。由于map是全局的,多个协程会同时访问,属于临界区,因此添加了读写锁。如下定义

var (
	tlsDataMap  = map[unsafe.Pointer]*tlsData{}
	tlsMu       sync.RWMutex
	tlsUniqueID int64
)

通过Get/Set/Del接口来获取、添加和删除协程的数据。

协程的数据可能需要在协程结束时进行释放,go-tls引入了退出函数列表,遵循FILO规则执行。

type tlsData struct {
	id          int64
	data        dataMap
	atExitFuncs []func()
}

通过AtExit接口添加退出处理器。

然后获取协程的g通过汇编实现的

TEXT ·getg(SB), NOSPLIT, $0-8
    get_tls(CX) //MOVQ TLS, CX
    MOVQ    g(CX), AX //0(CX)(TLS*1)
    MOVQ    AX, ret+0(FP)
    RET

通过特殊的黑客技术替换了go的默认goexit为自定义hackedGoexit,以实现上面atExitFuncs 的调用。参见goexit.go

如此实现了协程的tls。可以一定程度上避免context的使用,当然这是和官方的声称不一致的。

风险

  1. 要考虑它的兼容性,即go编译器后期是否有这方面的更新。因为它使用了一些黑客技术,比如替换goexit的过程,这个和底层是强依赖的。
  2. 由于使用了读写锁,对性能敏感的需求请慎重。可能你会建议使用sync.Map替换普通map+读写锁的方式,其实我告诉你,个人已经单机压测过,它们两个性能相当,甚至后者比sync.Map还好那么一点点。
  3. 考虑维护成本;因为官方推荐context;
  4. 协程里起协程是无法传递tls的。

如果对以上四点不敏感的需求场景,可以放心使用。

猜你喜欢

转载自blog.csdn.net/my_live_123/article/details/88790794
TLS