Go大咖说

Go大咖说

仅限Beego使用者,谈一谈在Beego遇到的瓶颈与问题

技术讨论yuya 回复了问题 • 3 人关注 • 1 个回复 • 942 次浏览 • 2018-11-06 10:10 • 来自相关话题

仅限Iris-go使用者,谈一谈在Iris-go遇到的瓶颈与问题

技术讨论shaolei7788 回复了问题 • 3 人关注 • 1 个回复 • 1714 次浏览 • 2018-11-06 09:12 • 来自相关话题

Golang 获取 goroutine id 完全指南

文章分享AlexaMa 发表了文章 • 5 个评论 • 2225 次浏览 • 2018-03-06 17:40 • 来自相关话题

在Golang中,每个goroutine协程都有一个goroutine id (goid),该goid没有向应用层暴露。但是,在很多场景下,开发者又希望使用goid作为唯一标识,将一个goroutine中的函数层级调用串联起来。比如,希望在一个http han ...查看全部

在Golang中,每个goroutine协程都有一个goroutine id (goid),该goid没有向应用层暴露。但是,在很多场景下,开发者又希望使用goid作为唯一标识,将一个goroutine中的函数层级调用串联起来。比如,希望在一个http handler中将这个请求的每行日志都加上对应的goid以便于对这个请求处理过程进行跟踪和分析。

关于是否应该将goid暴露给应用层已经争论多年。基本上,Golang的开发者都一致认为不应该暴露goid([faq: document why there is no way to get a goroutine ID](https://github.com/golang/go/issues/22770)),主要有以下几点理由:

1. goroutine设计理念是轻量,鼓励开发者使用多goroutine进行开发,不希望开发者通过goid做goroutine local storage或thread local storage(TLS)的事情;
2. Golang开发者Brad认为TLS在C/C++实践中也问题多多,比如一些使用TLS的库,thread状态非常容易被非期望线程修改,导致crash.
3. goroutine并不等价于thread, 开发者可以通过syscall获取thread id,因此根本不需要暴露goid.

官方也一直推荐使用[context](https://blog.golang.org/context)作为上下文关联的最佳实践。如果你还是想获取goid,下面是我整理的目前已知的所有获取它的方式,希望你想清楚了再使用。


1. 通过stack信息获取goroutine id.
2. 通过修改源代码获取goroutine id.
3. 通过CGo获取goroutine id.
4. 通过汇编获取goroutine id.
5. 通过汇编获取**伪**goroutine id.


在开始介绍各种方法前,先看一下定义在`src/runtime/runtime2.go`中保存goroutine状态的`g`结构:

```
type g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink

_panic *_panic // innermost panic - offset known to liblink
_defer *_defer // innermost defer
m *m // current m; offset known to arm liblink
sched gobuf
syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
stktopsp uintptr // expected sp at top of stack, to check in traceback
param unsafe.Pointer // passed parameter on wakeup
atomicstatus uint32
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
goid int64 // goroutine id
...
```

其中`goid int64`字段即为当前goroutine的id。

## 1. 通过stack信息获取goroutine id

```
package main

import (
"bytes"
"fmt"
"runtime"
"strconv"
)

func main() {
fmt.Println(GetGID())
}

func GetGID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
}
```

原理非常简单,将stack中的文本信息"goroutine 1234"匹配出来。但是这种方式有两个问题:

1. stack信息的格式随版本更新可能变化,甚至不再提供goroutine id,可靠性差。
2. 性能较差,调用10000次消耗>50ms。

如果你只是想在个人项目中使用goid,这个方法是可以胜任的。维护和修改成本相对较低,且不需要引入任何第三方依赖。同时建议你就此打住,不要继续往下看了。

## 2. 通过修改源代码获取goroutine id

既然方法1效率较低,且不可靠,那么我们可以尝试直接修改源代码`src/runtime/runtime2.go`中添加`Goid`函数,将goid暴露给应用层:

```
func Goid() int64 {
_g_ := getg()
return _g_.goid
}
```

这个方式能解决法1的两个问题,但是会导致你的程序只能在修改了源代码的机器上才能编译,没有移植性,并且每次go版本升级以后,都需要重新修改源代码,维护成本较高。

## 3. 通过CGo获取goroutine id

那么有没有性能好,同时不影响移植性,且维护成本低的方法呢?那就是来自Dave Cheney的CGo方式:

文件id.c:

```
#include "runtime.h"

int64 ·Id(void) {
return g->goid;
}
```

文件id.go:

```
package id

func Id() int64
```

完整代码参见[junk/id](https://github.com/davecheney/junk/blob/master/id/id.c).

这种方法的问题在于你需要开启CGo, CGo存在一些缺点,具体可以参见这个大牛的[cgo is not Go](https://dave.cheney.net/2016/01/18/cgo-is-not-go). 我相信在你绝大部分的工程项目中,你是不希望开启CGo的。

## 4. 通过汇编获取goroutine id

如果前面三种方法我们都不能接受,有没有第四种方法呢?那就是通过汇编获取goroutine id的方法。原理是:通过getg方法(汇编实现)获取到当前goroutine的g结构地址,根据偏移量计算出成员`goid int`的地址,然后取出该值即可。

项目[goroutine](https://github.com/huandu/goroutine/tree/master)实现了这种方法。需要说明的是,这种方法看似简单,实际上因为每个go版本几乎都会有针对g结构的调整,因此`goid int64`的偏移并不是固定的,更加复杂的是,go在编译的时候,传递的编译参数也会影响`goid int64`的偏移值,因此,这个项目的作者花了非常多精力来维护每个go版本g结构偏移的计算,详见[hack](https://github.com/huandu/goroutine/tree/master/hack)目录。

这个方法性能好,原理清晰,实际使用上稳定性也不错(我们在部分不太重要的线上业务使用了这种方法)。但是,维护这个库也许真的太累了,最近发现作者将这个库标记为“DEPRECATED”,看来获取goroutine id是条越走越远的不归路


## 5. 通过汇编获取**伪**goroutine id

虽然方法4从原理和实际应用上表现都不错,但是毕竟作者弃坑了。回到我们要解决的问题上:我们并不是真的一定要获取到goroutine id,我们只是想获取到goroutine的唯一标识。那么,从这个角度看的话,我们只需要解决goroutine标识唯一性的问题即可。

显然,上面作者也想清楚了这个问题。他新开了一个库[go-tls](https://github.com/huandu/go-tls), 这个库实现了goroutine local storage,其中获取goroutine id的方式是:用方法4的汇编获取goroutine的地址,然后自己管理和分配goroutine id。由于它获取到的并不是真正的goroutine id,因此我将之称为**伪**goroutine id。其实现的核心代码如下:

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

...

func fetchDataMap(readonly bool) *tlsData {
gp := g.G() // 1. 获取g结构地址

if gp == nil {
return nil
}

// Try to find saved data.
needHack := false
tlsMu.Lock()
dm := tlsDataMap[gp]

if dm == nil && !readonly {
needHack = true
dm = &tlsData{
id: atomic.AddInt64(&tlsUniqueID, 1), // 2. 分配伪goroutine id
data: dataMap{},
}
tlsDataMap[gp] = dm
}

tlsMu.Unlock()

// Current goroutine is not hacked. Hack it.
if needHack {
if !hack(gp) {
tlsMu.Lock()
delete(tlsDataMap, gp)
tlsMu.Unlock()
}
}

return dm
}


```

1. 获取g结构地址。
2. 分配伪goroutine id.

这种方式基本没有什么不能接受的hack实现,从原理上来说也更加安全。但是获取到不是你最开始想要的goroutine id,不知你能否接受


## 小结

获取goroutine id是一条不归路,目前也没有完美的获取它的方式。如果你一定要使用goroutine id,先想清楚你要解决的问题是什么,如果没有必要,建议你不要走上这条不归路。尽早在团队中推广使用context, 越早使用越早脱离对goroutine id的留恋和挣扎。

## Credit

* [Goroutine IDs](https://blog.sgmansfield.com/2015/12/goroutine-ids/)
* [faq: document why there is no way to get a goroutine ID](https://github.com/golang/go/issues/22770)
* [获取Goroutine Id的最佳实践](https://www.jianshu.com/p/85a08d8e7af3)

**Originally published at [liudanking.com](https://liudanking.com/performance/golang-%E8%8E%B7%E5%8F%96-goroutine-id-%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/)**

go的compiler是如何支持append函数确定其slice类型的?

回复

有问必答cbsheng 发起了问题 • 1 人关注 • 0 个回复 • 1789 次浏览 • 2018-02-01 10:46 • 来自相关话题

GopherChina 2018 开始了

文章分享astaxie 发表了文章 • 1 个评论 • 1847 次浏览 • 2017-12-28 23:23 • 来自相关话题

经过半个多月的筹划,GopherChina 2018正式开始对外了,明年我们继续在4.14-4.15期间在上海举办,目前已经确认的国外嘉宾有《Go in Action》作者,Dgraph作者,Go Team的人还在确认中,国内确认的嘉宾有探探后端负责人Henr ...查看全部
经过半个多月的筹划,GopherChina 2018正式开始对外了,明年我们继续在4.14-4.15期间在上海举办,目前已经确认的国外嘉宾有《Go in Action》作者,Dgraph作者,Go Team的人还在确认中,国内确认的嘉宾有探探后端负责人Henry,容器引擎hyper的CTO 王旭,其他嘉宾正在逐步确认中,目前开始早鸟票时间段,大家抓紧时间抢。

今年我们还很荣幸邀请到了William Kennedy,他作为美国Gopher大会的组织者,同时又是专业的Go培训师,我们今年也打算举行会前一天的workshop。

https://www.bagevent.com/event/1086224

【Go大咖说】 分享第一期——姜家志《比特币、区块链和Go开发》

文章分享astaxie 发表了文章 • 9 个评论 • 3307 次浏览 • 2017-07-17 16:37 • 来自相关话题

GoCN社区最近打算推出全新的栏目《Go大咖说》,第一期我们邀请到了来自比特大陆的高级工程师姜家志给我们带来分享《比特币、区块链和Go开发》 个人介绍: 姜家志,2013年开始接触比特币,之后开发了比太钱包,现在在比特大陆 ...查看全部
GoCN社区最近打算推出全新的栏目《Go大咖说》,第一期我们邀请到了来自比特大陆的高级工程师姜家志给我们带来分享《比特币、区块链和Go开发》

个人介绍:
姜家志,2013年开始接触比特币,之后开发了比太钱包,现在在比特大陆从事比特币相关的开发工作。

![](http://ww2.sinaimg.cn/mw690/76feee57jw1eb2xtb80j2j20hs0hsgod.jpg)

大纲:

- 比特币是什么
- 区块链是什么以及区块链行业现状
- Go语言在区块链行业的应用以及优势

我们的分享会在我们的Go实战群直播,会同步转发到全部的实战群,大家可以加微信群或者QQ群,

直播时间:周二(2017-07-18) 晚上8点开始

微信:请加fuxiaohei,备注:Go实战群
QQ: 148647580

后续会整理成文发布在Go中国微信公众号,大家可以关注我们的公众号:![](https://github.com/gocn/images/blob/master/qrcode_for_gh_83ea6a23d55b_258.jpg?raw=true)

仅限Beego使用者,谈一谈在Beego遇到的瓶颈与问题

回复

技术讨论yuya 回复了问题 • 3 人关注 • 1 个回复 • 942 次浏览 • 2018-11-06 10:10 • 来自相关话题

仅限Iris-go使用者,谈一谈在Iris-go遇到的瓶颈与问题

回复

技术讨论shaolei7788 回复了问题 • 3 人关注 • 1 个回复 • 1714 次浏览 • 2018-11-06 09:12 • 来自相关话题

go的compiler是如何支持append函数确定其slice类型的?

回复

有问必答cbsheng 发起了问题 • 1 人关注 • 0 个回复 • 1789 次浏览 • 2018-02-01 10:46 • 来自相关话题

Golang 获取 goroutine id 完全指南

文章分享AlexaMa 发表了文章 • 5 个评论 • 2225 次浏览 • 2018-03-06 17:40 • 来自相关话题

在Golang中,每个goroutine协程都有一个goroutine id (goid),该goid没有向应用层暴露。但是,在很多场景下,开发者又希望使用goid作为唯一标识,将一个goroutine中的函数层级调用串联起来。比如,希望在一个http han ...查看全部

在Golang中,每个goroutine协程都有一个goroutine id (goid),该goid没有向应用层暴露。但是,在很多场景下,开发者又希望使用goid作为唯一标识,将一个goroutine中的函数层级调用串联起来。比如,希望在一个http handler中将这个请求的每行日志都加上对应的goid以便于对这个请求处理过程进行跟踪和分析。

关于是否应该将goid暴露给应用层已经争论多年。基本上,Golang的开发者都一致认为不应该暴露goid([faq: document why there is no way to get a goroutine ID](https://github.com/golang/go/issues/22770)),主要有以下几点理由:

1. goroutine设计理念是轻量,鼓励开发者使用多goroutine进行开发,不希望开发者通过goid做goroutine local storage或thread local storage(TLS)的事情;
2. Golang开发者Brad认为TLS在C/C++实践中也问题多多,比如一些使用TLS的库,thread状态非常容易被非期望线程修改,导致crash.
3. goroutine并不等价于thread, 开发者可以通过syscall获取thread id,因此根本不需要暴露goid.

官方也一直推荐使用[context](https://blog.golang.org/context)作为上下文关联的最佳实践。如果你还是想获取goid,下面是我整理的目前已知的所有获取它的方式,希望你想清楚了再使用。


1. 通过stack信息获取goroutine id.
2. 通过修改源代码获取goroutine id.
3. 通过CGo获取goroutine id.
4. 通过汇编获取goroutine id.
5. 通过汇编获取**伪**goroutine id.


在开始介绍各种方法前,先看一下定义在`src/runtime/runtime2.go`中保存goroutine状态的`g`结构:

```
type g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink

_panic *_panic // innermost panic - offset known to liblink
_defer *_defer // innermost defer
m *m // current m; offset known to arm liblink
sched gobuf
syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
stktopsp uintptr // expected sp at top of stack, to check in traceback
param unsafe.Pointer // passed parameter on wakeup
atomicstatus uint32
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
goid int64 // goroutine id
...
```

其中`goid int64`字段即为当前goroutine的id。

## 1. 通过stack信息获取goroutine id

```
package main

import (
"bytes"
"fmt"
"runtime"
"strconv"
)

func main() {
fmt.Println(GetGID())
}

func GetGID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
}
```

原理非常简单,将stack中的文本信息"goroutine 1234"匹配出来。但是这种方式有两个问题:

1. stack信息的格式随版本更新可能变化,甚至不再提供goroutine id,可靠性差。
2. 性能较差,调用10000次消耗>50ms。

如果你只是想在个人项目中使用goid,这个方法是可以胜任的。维护和修改成本相对较低,且不需要引入任何第三方依赖。同时建议你就此打住,不要继续往下看了。

## 2. 通过修改源代码获取goroutine id

既然方法1效率较低,且不可靠,那么我们可以尝试直接修改源代码`src/runtime/runtime2.go`中添加`Goid`函数,将goid暴露给应用层:

```
func Goid() int64 {
_g_ := getg()
return _g_.goid
}
```

这个方式能解决法1的两个问题,但是会导致你的程序只能在修改了源代码的机器上才能编译,没有移植性,并且每次go版本升级以后,都需要重新修改源代码,维护成本较高。

## 3. 通过CGo获取goroutine id

那么有没有性能好,同时不影响移植性,且维护成本低的方法呢?那就是来自Dave Cheney的CGo方式:

文件id.c:

```
#include "runtime.h"

int64 ·Id(void) {
return g->goid;
}
```

文件id.go:

```
package id

func Id() int64
```

完整代码参见[junk/id](https://github.com/davecheney/junk/blob/master/id/id.c).

这种方法的问题在于你需要开启CGo, CGo存在一些缺点,具体可以参见这个大牛的[cgo is not Go](https://dave.cheney.net/2016/01/18/cgo-is-not-go). 我相信在你绝大部分的工程项目中,你是不希望开启CGo的。

## 4. 通过汇编获取goroutine id

如果前面三种方法我们都不能接受,有没有第四种方法呢?那就是通过汇编获取goroutine id的方法。原理是:通过getg方法(汇编实现)获取到当前goroutine的g结构地址,根据偏移量计算出成员`goid int`的地址,然后取出该值即可。

项目[goroutine](https://github.com/huandu/goroutine/tree/master)实现了这种方法。需要说明的是,这种方法看似简单,实际上因为每个go版本几乎都会有针对g结构的调整,因此`goid int64`的偏移并不是固定的,更加复杂的是,go在编译的时候,传递的编译参数也会影响`goid int64`的偏移值,因此,这个项目的作者花了非常多精力来维护每个go版本g结构偏移的计算,详见[hack](https://github.com/huandu/goroutine/tree/master/hack)目录。

这个方法性能好,原理清晰,实际使用上稳定性也不错(我们在部分不太重要的线上业务使用了这种方法)。但是,维护这个库也许真的太累了,最近发现作者将这个库标记为“DEPRECATED”,看来获取goroutine id是条越走越远的不归路


## 5. 通过汇编获取**伪**goroutine id

虽然方法4从原理和实际应用上表现都不错,但是毕竟作者弃坑了。回到我们要解决的问题上:我们并不是真的一定要获取到goroutine id,我们只是想获取到goroutine的唯一标识。那么,从这个角度看的话,我们只需要解决goroutine标识唯一性的问题即可。

显然,上面作者也想清楚了这个问题。他新开了一个库[go-tls](https://github.com/huandu/go-tls), 这个库实现了goroutine local storage,其中获取goroutine id的方式是:用方法4的汇编获取goroutine的地址,然后自己管理和分配goroutine id。由于它获取到的并不是真正的goroutine id,因此我将之称为**伪**goroutine id。其实现的核心代码如下:

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

...

func fetchDataMap(readonly bool) *tlsData {
gp := g.G() // 1. 获取g结构地址

if gp == nil {
return nil
}

// Try to find saved data.
needHack := false
tlsMu.Lock()
dm := tlsDataMap[gp]

if dm == nil && !readonly {
needHack = true
dm = &tlsData{
id: atomic.AddInt64(&tlsUniqueID, 1), // 2. 分配伪goroutine id
data: dataMap{},
}
tlsDataMap[gp] = dm
}

tlsMu.Unlock()

// Current goroutine is not hacked. Hack it.
if needHack {
if !hack(gp) {
tlsMu.Lock()
delete(tlsDataMap, gp)
tlsMu.Unlock()
}
}

return dm
}


```

1. 获取g结构地址。
2. 分配伪goroutine id.

这种方式基本没有什么不能接受的hack实现,从原理上来说也更加安全。但是获取到不是你最开始想要的goroutine id,不知你能否接受


## 小结

获取goroutine id是一条不归路,目前也没有完美的获取它的方式。如果你一定要使用goroutine id,先想清楚你要解决的问题是什么,如果没有必要,建议你不要走上这条不归路。尽早在团队中推广使用context, 越早使用越早脱离对goroutine id的留恋和挣扎。

## Credit

* [Goroutine IDs](https://blog.sgmansfield.com/2015/12/goroutine-ids/)
* [faq: document why there is no way to get a goroutine ID](https://github.com/golang/go/issues/22770)
* [获取Goroutine Id的最佳实践](https://www.jianshu.com/p/85a08d8e7af3)

**Originally published at [liudanking.com](https://liudanking.com/performance/golang-%E8%8E%B7%E5%8F%96-goroutine-id-%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/)**

GopherChina 2018 开始了

文章分享astaxie 发表了文章 • 1 个评论 • 1847 次浏览 • 2017-12-28 23:23 • 来自相关话题

经过半个多月的筹划,GopherChina 2018正式开始对外了,明年我们继续在4.14-4.15期间在上海举办,目前已经确认的国外嘉宾有《Go in Action》作者,Dgraph作者,Go Team的人还在确认中,国内确认的嘉宾有探探后端负责人Henr ...查看全部
经过半个多月的筹划,GopherChina 2018正式开始对外了,明年我们继续在4.14-4.15期间在上海举办,目前已经确认的国外嘉宾有《Go in Action》作者,Dgraph作者,Go Team的人还在确认中,国内确认的嘉宾有探探后端负责人Henry,容器引擎hyper的CTO 王旭,其他嘉宾正在逐步确认中,目前开始早鸟票时间段,大家抓紧时间抢。

今年我们还很荣幸邀请到了William Kennedy,他作为美国Gopher大会的组织者,同时又是专业的Go培训师,我们今年也打算举行会前一天的workshop。

https://www.bagevent.com/event/1086224

【Go大咖说】 分享第一期——姜家志《比特币、区块链和Go开发》

文章分享astaxie 发表了文章 • 9 个评论 • 3307 次浏览 • 2017-07-17 16:37 • 来自相关话题

GoCN社区最近打算推出全新的栏目《Go大咖说》,第一期我们邀请到了来自比特大陆的高级工程师姜家志给我们带来分享《比特币、区块链和Go开发》 个人介绍: 姜家志,2013年开始接触比特币,之后开发了比太钱包,现在在比特大陆 ...查看全部
GoCN社区最近打算推出全新的栏目《Go大咖说》,第一期我们邀请到了来自比特大陆的高级工程师姜家志给我们带来分享《比特币、区块链和Go开发》

个人介绍:
姜家志,2013年开始接触比特币,之后开发了比太钱包,现在在比特大陆从事比特币相关的开发工作。

![](http://ww2.sinaimg.cn/mw690/76feee57jw1eb2xtb80j2j20hs0hsgod.jpg)

大纲:

- 比特币是什么
- 区块链是什么以及区块链行业现状
- Go语言在区块链行业的应用以及优势

我们的分享会在我们的Go实战群直播,会同步转发到全部的实战群,大家可以加微信群或者QQ群,

直播时间:周二(2017-07-18) 晚上8点开始

微信:请加fuxiaohei,备注:Go实战群
QQ: 148647580

后续会整理成文发布在Go中国微信公众号,大家可以关注我们的公众号:![](https://github.com/gocn/images/blob/master/qrcode_for_gh_83ea6a23d55b_258.jpg?raw=true)