/images/hugo/avatar.png

Go unsafe包的安全使用模式

1. 类型安全

在Go语言中,我们是无法通过常规语法手段穿透Go在类型系统层面对内存数据的保护的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
    a := 0x12345678
    fmt.Printf("0x%x\n", a)

    var p *byte = (*byte)(&a)   // 错误!不允许将&a从*int类型显式转型为*byte类型
    *p = 0x23

    var b byte = byte(a)        // b是一个新变量,有自己所解释的内存空间
    b = 0x23                    // 即便强制进行类型转换,原变量a所解释的内存空间的数据依然不变
    fmt.Printf("0x%x\n", b)     // 0x23
    fmt.Printf("0x%x\n", a)     // 0x12345678
}

Go在常规操作下是类型安全的(注:并非绝对的类型安全,绝对的类型安全需要在数学上的形式化证明)。所谓类型安全是指一块内存数据一旦被特定的类型所解释(该内存数据与该类型变量建立关联,也就是变量定义),它就不能再被解释为其他类型,不能再与其他类型变量建立关联。

Go 网络编程

Go是自带运行时的跨平台编程语言,Go中暴露给语言使用者的TCP Socket接口是建立在操作系统原生TCP Socket接口之上的。由于Go运行时调度的需要,Go设计了一套适合自己的TCP Socket网络编程模型。在本条中我们就来理解一下这个模型,并了解在该模型下Go TCP Socket在各个场景下的使用方法、行为特点及注意事项。

Go 调度器

今天我们来学习一些 Go 程序的调试技巧。

1. 调度器状态的查看方法

Go提供了调度器当前状态的查看方法:使用Go运行时环境变量GODEBUG。

1
2
3
4
5
6
$ GODEBUG=schedtrace=1000 godoc -http=:6060
SCHED 0ms: gomaxprocs=4 idleprocs=3 threads=3 spinningthreads=0 idlethreads=0 runqueue=0 [0 0 0 0]
SCHED 1001ms: gomaxprocs=4 idleprocs=0 threads=9 spinningthreads=0 idlethreads=3 runqueue=2 [8 14 5 2]
SCHED 2006ms: gomaxprocs=4 idleprocs=0 threads=25 spinningthreads=0 idlethreads=19 runqueue=12 [0 0 4 0]
SCHED 3006ms: gomaxprocs=4 idleprocs=0 threads=26 spinningthreads=0 idlethreads=8 runqueue=2 [0 1 1 0]
SCHED 4010ms: gomaxprocs=4 idleprocs=0 threads=26 spinningthreads=0 idlethreads=20 runqueue=12 [6 3 1 0]

GODEBUG 通过给其传入不同的key1=value1, key2=value2, …组合,Go的运行时会输出不同的调试信息,比如在这里我们给GODEBUG传入了"schedtrace=1000",其含义就是每1000ms打印输出一次goroutine调度器的状态,每次一行。以上面例子中最后一行为例,每一行各字段含义如下:

Go 汇编反汇编

1. 查看Go程序的汇编代码

查看Go程序的汇编代码有多种方法:

  1. 使用objdump工具:objdump -S go二进制文件
  2. 使用gdb disassemble
  3. 使用go tool工具生成汇编代码文件:go build -gcflags '-S' xx.go > xx.s 2>&1
  4. 将Go代码编译成汇编代码:go tool compile -S xx.go > xx.s
  5. 使用go tool工具反编译Go程序:go tool objdump -S go-binary > xx.s
1
2
3
4
// 编译
GOARCH=386 go tool compile -N -l test.go
// 反编译
GOARCH=386 go tool objdump -gnu test.o

交叉编译

1
2
3
4
GOOS=linux GOARCH=amd64 go build

// 交叉编译支持的所有架构
$GOROOT/src/go/build/syslist.go

参考资料

  1. Debugging Performance Issues in Go Programs
  2. go.dev/doc/diagnostics
  3. go-perfbook
  4. talk-yapc-asia-2015

Delve 调试Go代码

1. 专业调试工具

bug是表象,要发现内部原因需要利用更多的表象数据去推理,需要收集足够多的“现场数据”。我们可以通过编程语言内置的输出语句(如Go的print、fmt.Printf等)输出我们需要的信息,而更为专业的方法是通过编程语言提供的专业调试工具(如GDB)设置断点来采集现场数据或重现bug。

expvar

1. expvar

应用运行状态一般以度量数据的形式呈现。通过了解应用关键路径上的度量数据,我们可以确定在某个度量点上应用的性能是符合预期性能指标还是较大偏离预期,这样就可以最大限度地缩小性能瓶颈点的搜索范围,从而快速定位应用中的瓶颈点并进行优化。这些可以反映应用运行状态的数据也被称为应用的内省(introspection)数据。相比于通过查询应用外部特征而获取的探针类(probing)数据(比如查看应用某端口是否有响应并返回正确的数据或状态码),内省数据可以传达更为丰富、更多的有关应用程序状态的上下文信息。这些上下文信息可以是应用对各类资源的占用信息,比如应用运行占用了多少内存空间,也可以是自定义的性能指标信息,比如单位时间处理的外部请求数量、应答延迟、队列积压量等。我们可以轻松地使用Go标准库提供的expvar包按统一接口、统一数据格式、一致的指标定义方法输出自定义的度量数据。