线上调试Go语言实战技巧

简介

  • 使用过python的朋友们都知道python中有自带的pdb命令行调试功能,同样,go语言也有官方提供的gdb调试,但是特别不好用,尤其是对go routinue调试很鸡肋,我们先看下官方的解释:
GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo.

In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success.

In time, a more Go-centric debugging architecture may be required.

于是作者调查发现目前市面上用的更多的是dlv调试,不仅支持gdb所有的功能,还对go routinue有很好的支持,大家知道调试的时候,单线程是最好调试的,dlv就有这样的优点,下面我们来看下怎么玩起来。

安装

  • go语言安装和环境配置,这里
  • dlv安装:
$ go get github.com/derekparker/delve/cmd/dlv

ps. 默认会安装到$GOPATH/src/github.com/下面

  • 配置dlv:
$ vim ~/.bash_profile
alias dlv="~/go/bin/dlv"   (一般安装完了会在$GOPATH/bin下生成二进制可运行文件)
$ source ~/.bash_profile
  • 运行命令$ dlv debug --help,出现一些命令说明成功

运行

  • 我们先给一段测试代码(dlv.go):
package main

import (
	"fmt"
	"sync"
	"time"
)

func doing(wg *sync.WaitGroup, i int) {
	fmt.Printf("start goroutine id %d\n", i)
	time.Sleep(2 * time.Second)
	fmt.Printf("finish goroutine id %d\n", i)
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	workers := 10

	wg.Add(workers)
	for i := 0; i < workers; i++ {
		go doing(&wg, i)
	}
	wg.Wait()

}

基本功能

  • debug模式:
$ dlv debug dlv.go

powerdeMBP:interface power$ dlv debug dlv.go
Type 'help' for list of commands.
(dlv)
  • break(设置断点):
(dlv) break main.main
Breakpoint 1 set at 0x10b27c3 for main.main() ./dlv.go:16
(dlv)
  • continue(快捷键:c), 走到断点的位置:
(dlv) continue
> main.main() ./dlv.go:16 (hits goroutine(1):1 total:1) (PC: 0x10b27c3)
    11:		time.Sleep(2 * time.Second)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
=>  16:	func main() {
    17:		var wg sync.WaitGroup
    18:		workers := 10
    19:
    20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
(dlv)
  • next(快捷键:n), 往下走一步,读者可以多试两次:
(dlv) next
> main.main() ./dlv.go:17 (PC: 0x10b27d1)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
    16:	func main() {
=>  17:		var wg sync.WaitGroup
    18:		workers := 10
    19:
    20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
    22:			go doing(&wg, i)
(dlv) next
> main.main() ./dlv.go:18 (PC: 0x10b27fa)
    13:		wg.Done()
    14:	}
    15:
    16:	func main() {
    17:		var wg sync.WaitGroup
=>  18:		workers := 10
    19:
    20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
    22:			go doing(&wg, i)
    23:		}
  • print(快捷键:p),打印变量
(dlv) n
> main.main() ./dlv.go:20 (PC: 0x10b2803)
    15:
    16:	func main() {
    17:		var wg sync.WaitGroup
    18:		workers := 10
    19:
=>  20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
    22:			go doing(&wg, i)
    23:		}
    24:		wg.Wait()
    25:
(dlv) p workers
10
  • 设置断点到函数doing, 我们来看看开启十个go routinue后什么情况。
(dlv) break doing
Breakpoint 2 set at 0x10b2598 for main.doing() ./dlv.go:9
(dlv) c
> main.doing() ./dlv.go:9 (hits goroutine(10):1 total:4) (PC: 0x10b2598)
> main.doing() ./dlv.go:9 (hits goroutine(6):1 total:4) (PC: 0x10b2598)
> main.doing() ./dlv.go:9 (hits goroutine(5):1 total:4) (PC: 0x10b2598)
> main.doing() ./dlv.go:9 (hits goroutine(14):1 total:4) (PC: 0x10b2598)
     4:		"fmt"
     5:		"sync"
     6:		"time"
     7:	)
     8:
=>   9:	func doing(wg *sync.WaitGroup, i int) {
    10:		fmt.Printf("start goroutine id %d\n", i)
    11:		time.Sleep(2 * time.Second)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
(dlv)
  • 打印任务id多少,看看我们进入了第几个go routinue:
(dlv) n
> main.doing() ./dlv.go:10 (PC: 0x10b25af)
     5:		"sync"
     6:		"time"
     7:	)
     8:
     9:	func doing(wg *sync.WaitGroup, i int) {
=>  10:		fmt.Printf("start goroutine id %d\n", i)
    11:		time.Sleep(2 * time.Second)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
(dlv) p i
9
(dlv)

ps. 我们发现可以单独对其中一个go routinue进行调试,也就是可以在多线程中对某个线程(在go语言中应该叫协程),更棒的还在后面,dlv还能对运行之中的程序进行调试。

高级功能

  • 我们还是使用上面那一套程序(dlv.go),将time.sleep设置大一些,这样有时间操作
package main

import (
	"fmt"
	"sync"
	"time"
)

func doing(wg *sync.WaitGroup, i int) {
	fmt.Printf("start goroutine id %d\n", i)
	// 这里设置大一些
	time.Sleep(100 * time.Second)
	fmt.Printf("finish goroutine id %d\n", i)
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	workers := 10

	wg.Add(workers)
	for i := 0; i < workers; i++ {
		go doing(&wg, i)
	}
	wg.Wait()

}
  • 我们打开两个终端,一个用来运行,一个用来调试运行中的程序

  • 第一个终端编译并且运行:

$ go build dlv.go && ./dlv
  • 第二个终端查看运行中的pid (下面可以看到pid是15426)
power:PKUChain power$ ps
  PID TTY           TIME CMD
  15426 ttys000    0:00.00 ./dlv
  • 在程序运行结束前,我们可以使用attach命令进入程序中进行调试:
powerdeMBP:PKUChain power$ dlv attach 15426
Type 'help' for list of commands.
(dlv)
  • 设置断点到doing函数的第三行,因为go routinue基本都在第二行time.Sleep(100 * time.Second)阻塞了,我们在这句话运行完之前在下面一行打好断点等待。
(dlv) break doing:3
Breakpoint 1 set at 0x10931ff for main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12
(dlv) continue
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(12):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(6):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(5):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(7):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
     7:	)
     8:
     9:	func doing(wg *sync.WaitGroup, i int) {
    10:		fmt.Printf("start goroutine id %d\n", i)
    11:		time.Sleep(100 * time.Second)
=>  12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
    16:	func main() {
    17:		var wg sync.WaitGroup
(dlv)

ps. break (函数名): (函数第几行)

其他

  • 以上都是日常用到的操作,还有一些更高级的操作请看官方文档
  • 命令行调试还是非常重要的,有时候代码上线是没有ide给你使用的,这个时候优势就体现出来了,使用习惯了还是非常好用的。

参考资料

[1] go语言安装配置
[2] delve 官方文档

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值