ThreadLocal for Golang
背景
由于官方不支持 ThreadLocal,在业务中传参经常需要传递 context,造成参数混乱,开发效率低下,跨方法实现传参变得困难。
需要解决的核心问题:
1、 数据存储,g.labels unsafe.Pointer 字段在业务场景下不会被使用,所以我们可以把数据的指针存储在该字段上。
2、g.labels字段存在一个问题就是在创建子协程的时候指针会被自动复制,而数据不会。所以我们需要在访问该字段时,断该字段的内容是否为当前协程创建的,如果不是则清空。所以这就要求我们需要首先获取 goid,goid 的获取下文会进行说明。
3、垃圾回收,由于数据指针存储在协程结构 g.labels 字段,该字段在协程结束后自动被置为 nil,所以在下次垃圾回收时只要关联的数据没有被额外的引用便会自动回收。
架构
Goid
github 上有很多库已经实现了不同平台的汇编调用 go runtime 库中的 getg()。
然后通过 unsafe 便宜一定的地址空间获取 goid, 该方法与直接调用耗时相当,性能非常高。
在不同的平台上(windows、linux、mac)均能正常获取到。
在极特殊的情况下(目前未发现),如果获取不到会通过 runtime.Stack(false) 方式获取并打印警告日志,此时性能会降低,但能保证不出错。
FastThreadLocal
此设计参考了 netty 中的 FastThreadLocal。
将 threadLocalMap 数据结构由 Map 改为 Slice,在创建 ThreadLocal 实例的时候内部维护一个 id,该 id 为全局自增。
ThreadLocal 实例的 id 字段作为 Slice 的下标,这样在 Get,Set 操作时由原来的操作 Map 变更为通过下标操作 Slice,当 ThreadLocal 实例多的时候性能提升明显。
InheritableThreadLocal 实现
参考 java,在创建协程的时候先复制当前协程的 threadLocalMap,当任务真正的在子协程执行的时候,将复制出来的 threadLocalMap 赋值到当前协程,便可实现跨协程继承数据。
注意,如果是非指针的值数据继承过去的是复制后的值,而指针类型的数据跨协程继承的数据复制的是指针的值。
即在另一个协程对结构内部的字段内容进行修改实际上会影响两个协程。如果要避免这种情况的出现,需要实现 Cloneable 接口。此时复制的时候会将指针指向的内容进行复制一份。
垃圾回收
由于存储数据指针的g.labels字段会被自动清空,所以不需要额外的垃圾回收。
安装
go get github.com/timandy/routine
使用
以下代码简单演示了ThreadLocal的创建、设置、获取、跨协程传播等:
package main
import (
"fmt"
"github.com/timandy/routine"
"time"
)
var threadLocal = routine.NewThreadLocal()
var inheritableThreadLocal = routine.NewInheritableThreadLocal()
func main() {
threadLocal.Set("hello world")
inheritableThreadLocal.Set("Hello world2")
fmt.Println("threadLocal:", threadLocal.Get())
fmt.Println("inheritableThreadLocal:", inheritableThreadLocal.Get())
// 其他协程无法读取之前赋值的“hello world”。
go func() {
fmt.Println("threadLocal in goroutine:", threadLocal.Get())
fmt.Println("inheritableThreadLocal in goroutine:", inheritableThreadLocal.Get())
}()
// 但是,可以通过 Go 函数启动一个新的 goroutine。当前主 goroutine 的所有可继承变量都可以自动传递。
routine.Go(func() {
fmt.Println("threadLocal in goroutine by Go:", threadLocal.Get())
fmt.Println("inheritableThreadLocal in goroutine by Go:", inheritableThreadLocal.Get())
})
time.Sleep(time.Second)
}
执行结果为:
threadLocal: hello world
inheritableThreadLocal: Hello world2
threadLocal in goroutine: <nil>
inheritableThreadLocal in goroutine: <nil>
threadLocal in goroutine by Go: <nil>
inheritableThreadLocal in goroutine by Go: Hello world2
支持网格
darwin |
linux |
windows |
freebsd |
||
---|---|---|---|---|---|
386 |
386 |
||||
amd64 |
amd64 |
||||
armv6 |
armv6 |
||||
armv7 |
armv7 |
||||
arm64 |
arm64 |
||||
ppc64 |
ppc64 |
||||
s390x |
s390x |
||||
darwin |
linux |
windows |
freebsd |
:支持
源码
https://github.com/timandy/routine
ThreadLocal for Golang的更多相关文章
- idou老师教你学Istio 08: 调用链埋点是否真的“零修改”?
本文将结合一个具体例子中的细节详细描述Istio调用链的原理和使用方式.并基于Istio中埋点的原理解释来说明:为了输出一个质量良好的调用链,业务程序需根据自身特点做适当的修改,即并非官方一直在说的完 ...
- Golang源码探索(二) 协程的实现原理
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...
- Golang源码探索(二) 协程的实现原理(转)
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱,虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底 ...
- 深入Golang调度器之GMP模型
前言 随着服务器硬件迭代升级,配置也越来越高.为充分利用服务器资源,并发编程也变的越来越重要.在开始之前,需要了解一下并发(concurrency)和并行(parallesim)的区别. 并发: 逻 ...
- 面试必问:Golang高阶-Golang协程实现原理
引言 实现并发编程有进程,线程,IO多路复用的方式.(并发和并行我们这里不区分,如果CPU是多核的,可能在多个核同时进行,我们叫并行,如果是单核,需要排队切换,我们叫并发) 进程和线程的区别 进程是计 ...
- 说说Golang goroutine并发那些事儿
摘要:今天我们一起盘点一下Golang并发那些事儿. Golang.Golang.Golang 真的够浪,今天我们一起盘点一下Golang并发那些事儿,准确来说是goroutine,关于多线程并发,咱 ...
- 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...
- Golang, 以17个简短代码片段,切底弄懂 channel 基础
(原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...
- ThreadLocal简单理解
在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...
随机推荐
- 技术分享 | 想做App测试就一定要了解的App结构
本文节选自霍格沃兹测试开发学社内部教材 app 的结构包含了 APK 结构和 app 页面结构两个部分 APK结构 APK 是 Android Package 的缩写,其实就是 Android 的安装 ...
- DevStream 成为 CNCF Sandbox 项目啦!- 锣鼓喧天、鞭炮齐鸣、红旗招展、忘词了。
开局两张图,内容全靠"编" 来,有图有真相! DevStream ️ CNCF DevStream joins CNCF Sandbox CNCF Cloud Native Int ...
- 一张图进阶 RocketMQ - NameServer
前言 「三此君看了好几本书,看了很多遍源码整理的 一张图进阶 RocketMQ 图片链接,关于 RocketMQ 你只需要记住这张图!觉得不错的话,记得点赞关注哦.」 一张图进阶 RocketMQ 图 ...
- powershell命令总结
2021-07-21 初稿 ps命令采用动词-名词的方式命名,不区分大小写.默认当前文件夹为当前路径./.除去-match使用正则表达式匹配外,其他都使用*和?通配符. 速查 管道命令 前一个的输出作 ...
- 关于Vue的几个实用知识点(持续更新中……)
前言 排名不分先后,按自己习惯来的. 一.provide.inject 高级组件 总述: provide在父组件中定义,inject 在子孙组件中定义. provide:选项应该是一个对象或返回一个对 ...
- VueX的热更替你知道多少?
前言 我们在使用Vuex的时候,会时不时的更改Vuex内的数据,但是页面不会随之更新,如果数据量大,一个数据依赖另一个数据的话,这样我们要是再刷新页面的话会把以前依赖的数据清空,效率特别低.所以,今天 ...
- XAMPP Apache安装时问题总结
昨天遇到的一个问题,安装XAMPP后无法启动Apache服务,本以为可能是一些系统服务文件被占用的原因,安装软件后需重启一遍.可是重启计算机后依然无法启动Apache服务,状态栏里总是显示" ...
- PTA(BasicLevel)-1031 查验身份证
一.问题定义 一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下:首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,1 ...
- JDK9对集合添加的优化of方法和Debug追踪
JDK9对集合添加的优化(of方法) JDK9的新特性: 1.List接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素 2.static List of (E- ...
- e.printStackTrace() 原理的分析
e.printStackTrace(); 先查看下源码 如图片中1所示,使用的是 PrintStreamOrWriter public void printStackTrace() { printSt ...