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 ...
随机推荐
- python中collections.OrderedDict()
import collections #from collections import OrderededDict my_orderDict=collections.OrderedDict(house ...
- 关闭StackExchange等平台的privacy收集窗口
技术背景 当我们打开一个StackExchange页面的时候,经常会出现一个很大的privacy收集窗口,而且不管怎么点都关闭不了,比如像下图这样: 如果屏幕足够大,影响可能也不是很大,但是关键是对于 ...
- c++ 快速乘
First 在一些数学题中,两个数相乘运算很多,同时又很容易溢出,如两个 long long 相乘 今天本蒟蒻来总结一下快速乘的两种方法 1:二进制 和快速幂的原理一样,优化一个一个加的算法,复杂度\ ...
- Docker容器固定ip
Docker容器固定IP 必须停止docker服务才能创建网桥 查看docker服务状态 停止docker服务 启动docker服务 [root@docker Tools]# systemctl st ...
- ExtJS 布局-Anchor 布局(Anchor layout)
更新记录: 2022年5月30日 发布本篇 1.说明 anchor布局类似auto布局从上到下进行堆叠,但不同的是其可以指定每个元素相对于容器大小的比例. 当调整父容器大小,容器根据指定的规则调整所有 ...
- Museui 图标速览,再也不用担心网页打不开了
更多内容请见原文,原文转载自:https://blog.csdn.net/weixin_44519496/article/details/119328173
- python小题目练习(13)
题目:封装用户的上网行为 实现代码: """Author:mllContent:封装用户的上网行为Date:2020-01-19"""def ...
- Integer.MAX_VALUE 和 Integer.MIN_VALUE
在源码中可以看出其对应的值 Integer.MAX_VALUE是2^31 -1 = 2147483647 Integer.MIN_VALUE是-2^31 = -2147483648
- linux查询文件或者文件夹
查找目录:find /(查找范围) -name '查找关键字' -type d // 查找fastdfs_storage_data文件夹 find / -name fastdfs_storage_da ...
- 初识Java GUI
1. 使用Java Swing 显示的窗口如下 在原有代码基础上添加代码实现对窗口大小 标题等信息