获取Goroutine Id的最佳实践
来源:互联网 发布:博士申请 知乎 编辑:程序博客网 时间:2024/05/17 07:27
大神的地址:http://www.jianshu.com/p/85a08d8e7af3
序言
在C/C++/Java等语言中,我们可以直接获取Thread Id,然后通过映射Thread Id和二级调度Task Id的关系,可以在日志中打印当前的TaskId,即用户不感知Task Id的打印,适配层统一封装,这使得多线程并发的日志的查看或过滤变得非常容易。
Goroutine是Golang中轻量级线程的实现,由Go Runtime管理。Golang在语言级别支持轻量级线程,叫携程。Golang标准库提供的所有系统调用操作(当然也包括所有同步IO操作),都会出让CPU给其他Goroutine。这让事情变得非常简单,让轻量级线程的切换管理不依赖于系统的线程和进程,也不依赖于CPU的核心数量。
Goroutine非常亮眼,但是自从go1.4版本以后,Goroutine Id无法直接从Go Runtime获取了。
这是Golang的开发者故意为之,避免开发者滥用Goroutine Id实现Goroutine Local Storage(类似java的Thread Local Storage), 因为Goroutine Local Storage很难进行垃圾回收。因此尽管Go1.4之前暴露出了相应的方法,现在已经把它隐藏了。
这个决策有点因噎废食,对于高并发日志的查看和过滤就变得比较困难。尽管在日志中可以使用业务本身的Id,但是在很多函数中仅仅为了打印而增加一些入参对于追求Clean Code的程序员实在无法接受。
笔者在本文中将找出一种简单高效稳定的解决方法,并给出最佳实践。
既有的几种方法
通过汇编获取
复杂度高,偏移地址随版本可能有变化,不建议使用
通过第三方库获取
相关的第三方库可以在github上找,比如:
https://github.com/jtolds/glshttps://github.com/huandu/goroutine
稳定性未知,性能也不高,不建议使用
通过runtime.Stack获取
它利用runtime.Stack的堆栈信息,将当前的堆栈信息写入到一个slice中,堆栈的第一行为 “goroutine #### […”,其中“####”就是当前的Goroutine Id,通过这个花招就可以实现Goid函数了。
采用该方法时,Goid函数的实现如下:
func Goid() int { defer func() { if err := recover(); err != nil { fmt.Println("panic recover:panic info:%v", err) } }() var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) } return id}
通过修改编译器源码获取
在go源码runtime包中增加函数Goid,直接调用runtime的getg函数获取,具有简单高效稳定的优点,同时每个团队可以通过容器来部署自己的微服务。
该方法将在“最佳实践”一节中详述。
方法三和方法四比较
分别采用方法三和方法四,将Goid函数连续调用10000次的性能数据如下:
方法三 方法四 > 50ms< 5us对于方法三,获取堆栈信息会影响性能,所以建议对性能不敏感的场景采用;
对于方法四,直接调用runtime的getg函数获取,效率最高,所以建议对性能有苛刻要求的场景采用。
本文关注性能,所以采用方法四。
最佳实践
下载go1.4版本的编译器
在Golang的官方网站下载go1.4版本的编译器,URL如下:
https://golang.org/dl/
解压缩,将go文件夹rename成go1.4,然后移动到$HOME目录下。
修改go1.7.3版本的编译器代码
在Golang的官方网站下载go1.7.3版本的源码。
编辑src/runtime/proc.go文件,在尾部添加函数Goid:
func Goid() int64 { _g_ := getg() return _g_.goid}
运行src/make.bash命令(默认使用$HOME/go1.4目录下的编译器),编译go1.7.3的新版本。
编译完成后,将go文件夹拷贝到GOROOT目录下,使之生效:
$ go versiongo version go1.7.3 linux/amd64
测试代码
我们模拟一个完全可以并行的计算任务:计算N个整型数的总和。我们可以将所有整型数分成M份,M即CPU的个数。让每个CPU开始计算分给它的那份计算任务,最后将每个CPU的计算结果再做一次累加,这样就可以得到所有N个整型数的总和,实现代码如下:
type Vector []intfunc (v Vector) DoSome(i, n int, u Vector, c chan int, add *int) int { for ; i < n; i++ { *add += u[i] } id := runtime.Goid(id) fmt.Println("id:", id) c <- 1 return 1}const NCPU = 16func (v Vector) DoAll(u Vector) int { c := make(chan int, NCPU) var add [NCPU]int sum := 0 for i := 0; i < NCPU; i++ { go v.DoSome(i * len(v) / NCPU, (i + 1)* len(v) / NCPU, u, c, &add[i]) } for i := 0; i < NCPU; i++ { <- c } for i := 0; i < NCPU; i++ { sum += add[i] } return sum}func main() { x := 0 y := 0 v := make(Vector, 160) for i := 0; i < 160; i++ { v[i] = i x += i } y = v.DoAll(v) fmt.Println("x =", x, "and y =", y)}
日志
通过查看日志,我们已将成功获取到了Goroutine Id。一个字,完美!
id: 20id: 13id: 7id: 12id: 14id: 9id: 5id: 17id: 16id: 10id: 6id: 15id: 18id: 19id: 8id: 11x = 12720 and y = 12720
适配层封装
我们可以将glog等第三方库的日志接口进行简单封装,隐藏goid的获取和打印过程,使得用户轻松。
小结
本文针对Golang中Goroutine的高并发的日志难以查看或过滤的问题,分析了既有的几种获取Goroutine Id的方法,最后找到一种简单高效稳定的方法,即通过修改编译器源码获取,并给出了最佳实践,希望对读者有一定的帮助。
作者:_张晓龙_
链接:http://www.jianshu.com/p/85a08d8e7af3
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 获取Goroutine Id的最佳实践
- 获取Goroutine Id的最佳实践
- goroutine使用最佳实践
- Golang获取goroutine ID
- 如何得到goroutine 的 id?
- Goroutine + Channel 实践
- Goroutine + Channel 实践
- Goroutine + Channel 实践
- Goroutine + Channel 实践
- Goroutine + Channel 实践
- MOS(Doc ID 1029252.6)最佳实践
- 关于备份和恢复的10 个最佳实践 (文档 ID 1549189.1)
- 最佳实践:针对性能问题的主动型数据收集 (文档 ID 1549179.1)
- mysql集群的最佳实践 Best Practices For MySQL Cluster (文档 ID 1926680.1)
- 更好的最佳实践
- Servlet的最佳实践
- 会话的最佳实践
- 多线程的最佳实践
- MySQL事务隔离级别详解
- coco评价指标
- Android8.0 电池用量 Battery usage data isn't available 问题
- 获取两点的偏移量(角度)
- c#委托
- 获取Goroutine Id的最佳实践
- 51nod 1127 最短的包含字符串 尺取法
- 使用jdbc访问日志服务
- 手写spring ioc文件配置版
- 算法爱好者——被围绕的区域 ? 待解决
- lens框架mtk
- JavaScript中的继承
- iOS友盟错误分析定位
- SQL Server移植