之前在 golang 群里有人问过为什么程序会莫名其妙的 hang 死然后不再响应任何请求。单核 cpu 打满。

这个特征和我们公司的某个系统曾经遇到的情况很相似,内部经过了很长时间的定位分析总结,期间还各种阅读 golang 的 runtime 和 gc 代码,最终才定位到是业务里出现了类型下面这样的代码:

package main

import "runtime"

func main() {
var ch = make(chan int, 100)
go func() {
for i := 0; i < 100; i++ {
ch <- 1
if i == 88 {
runtime.GC()
}
}
}() for {
// the wrong part
if len(ch) == 100 {
sum := 0
itemNum := len(ch)
for i := 0; i < itemNum; i++ {
sum += <-ch
}
if sum == itemNum {
return
}
}
} }

在 main goroutine 里循环判断 ch 里是否数据被填满,在另一个 goroutine 里把 100 条数据塞到 ch 里。看起来程序应该是可以正常结束的对不对?

感兴趣的话你可以自己尝试运行一下。实际上这个程序在稍微老一些版本的 golang 上是会卡死在后面这个 for 循环上的。原因呢?

使用:

GODEBUG="schedtrace=300,scheddetail=1" ./test1

应该可以看到这时候 gcwaiting 标记为 1。所以当初都怀疑是 golang gc 的 bug。。但最终折腾了半天才发现还是自己的代码的问题。

因为在 for 循环中没有函数调用的话,编译器不会插入调度代码,所以这个执行 for 循环的 goroutine 没有办法被调出,而在循环期间碰到 gc,那么就会卡在 gcwaiting 阶段,并且整个进程永远 hang 死在这个循环上。并不再对外响应。

当然,上面这段程序在最新版本的 golang 1.8/1.9 中已经不会 hang 住了(实验结果,没有深究原因)。某次更新说明中官方声称在密集循环中理论上也会让其它的 goroutine 有被调度的机会,那么我们选择相信官方,试一下下面这个程序:

package main

import (
"fmt"
"io"
"log"
"net/http"
"runtime"
"time"
) func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
go server()
go printNum()
var i = 1
for {
// will block here, and never go out
i++
}
fmt.Println("for loop end")
time.Sleep(time.Second * 3600)
} func printNum() {
i := 0
for {
fmt.Println(i)
i++
}
} func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
} func server() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":12345", nil) if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

运行几秒之后 curl 一发:

curl localhost:12345

感觉还是不要再相信官方了。研究研究之后不小心写出了这样的 bug 怎么定位比较好。首先分析一下这种类型 bug 发生时的程序特征:

1. 卡死在 for 循环上
2. gcwaiting=1
3. 没有系统调用

由于没有系统调用,不是系统调用导致的锅,所以我们没有办法借助 strace 之类的工具看程序是不是 hang 在系统调用上。而 gcwaiting=1 实际上并不能帮我们定位到问题到底出现在哪里。

然后就剩卡死在 for 循环上了,密集的 for 循环一般会导致一个 cpu 核心被打满。如果之前做过系统编程的同学应该对 perf 这个工具很了解,可以使用:

perf top

对 cpu 的使用情况进行采样,这样我们就可以对 cpu 使用排名前列的程序函数进行定位。实际上 perf top 的执行结果也非常直观:

  99.52%  ffff                     [.] main.main
0.06% [kernel] [k] __do_softirq
0.05% [kernel] [k] 0x00007fff81843a35
0.03% [kernel] [k] mpt_put_msg_frame
0.03% [kernel] [k] finish_task_switch
0.03% [kernel] [k] tick_nohz_idle_enter
0.02% perf [.] 0x00000000000824d7
0.02% [kernel] [k] e1000_xmit_frame
0.02% [kernel] [k] VbglGRPerform

你看,我们的程序实际上是卡在了 main.main 函数上。一发命令秒级定位。

妈妈再也不用担心我的程序不小心写出死循环了。实际上有时候我的一个普通循环为什么变成了死循环并不是像上面这样简单的 demo 那样好查,这时候你还可以用上 delve,最近就帮 jsoniter 定位了一个类似上面这样的 bug:

https://github.com/gin-gonic/gin/issues/1086

从 perf 定位到函数,再用 pid attach 到进程,找到正在执行循环的 goroutine,然后结合 locals 的打印一路 next。

问题定位 over。

如何定位 golang 进程 hang 死的 bug的更多相关文章

  1. 内核futex的BUG导致程序hang死问题排查

    https://mp.weixin.qq.com/s/sGS-Kw18sDnGEMfQrbPbVw 内核futex的BUG导致程序hang死问题排查 原创: 王领先 58架构师 今天   近日,Had ...

  2. 一次进程hang住问题分析。。。

    这两天有同学使用数据校验工具时发现进程hang住了,也不知道什么原因,我简单看了看进程堆栈,问题虽然很简单,但能导致程序hang住,也一定不是小问题.简单说明下程序组件的结构,程序由两部分构成,dbc ...

  3. 关于多线程情况下Net-SNMP v3 版本导致进程假死情况的跟踪与分析

    1.问题描述 在使用net-snmp对交换机进行扫描的时候经常会出现进程假死的情况(就是进程并没有死掉,但是看不到它与外界进行任何的数据交互).这时候不知道进程内部发生了什么,虽然有日志信息,但进程已 ...

  4. [转]gdb结合coredump定位崩溃进程

    [转]gdb结合coredump定位崩溃进程 http://blog.sina.com.cn/s/blog_54f82cc201013tk4.html Linux环境下经常遇到某个进程挂掉而找不到原因 ...

  5. 分析java进程假死状况

    摘自: http://www.myexception.cn/internet/2044496.html 分析java进程假死情况 1 引言 1.1 编写目的 为了方便大家以后发现进程假死的时候能够正常 ...

  6. 通过 profiling 定位 golang 性能问题 - 内存篇 原创 张威虎 滴滴技术 2019-08-02

    通过 profiling 定位 golang 性能问题 - 内存篇 原创 张威虎 滴滴技术 2019-08-02

  7. 关于用strace工具定位vrrpd进程有时会挂死的bug

    只做工作总结备忘之用. 正在烧镜像,稍总结一下进来改bug遇到的问题. 一个项目里要用到L3 switch的nat,vrrp功能,但实地测试中偶然出现write file挂死的情况,但不是必现.交付在 ...

  8. debug实战:进程Hang+High CPU

    最近几周都在解决程序不稳定的问题,具体表现为程序(多进程)时不时的Hang住,同时伴随某个进程的High CPU.跟踪下来,基本都是各种死锁引起的.这里选取一个典型的场景进行分析. 1.抓dump分析 ...

  9. 使用perf工具导致系统hang死的原因

    [perf工具导致系统hang住的原因是触发了低版本kernel的bug] 今天在测试服务器做压测,运行perf record做性能分析时,系统再次hang住了,这次在系统日志中记录了一些有用的信息, ...

随机推荐

  1. linux运维 技能 2018

    1.监控与日志 prometheus.grafana.zabbix ELK(elasticsearch logstash filebeat kibana) 2.容器类 harbor映像管理 docke ...

  2. C++的static_cast、dynamic_cast和const_cast用法

    static_cast.dynamic_cast和const_cast static_cast: ​ 用法: static_cast < type-id > (expression) ​ ...

  3. java中过滤器(Filter)与拦截器(Interceptor )区别

    过滤器(Filter) Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,主要的用途是设置字符集.控制权限.控制转向.做一些业务逻辑判断等.其工作 ...

  4. Mercurial 安装及使用

      版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/moonspiritacm/articl ...

  5. bad ELF interpreter: No such file or directory

    1.在64系统里执行32位程序如果出现/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory,安装下glic即可 yum ...

  6. 用python批量下载图片

    一 写爬虫注意事项 网络上有不少有用的资源, 如果需要合理的用爬虫去爬取资源是合法的,但是注意不要越界,前一阶段有个公司因为一个程序员写了个爬虫,导致公司200多个人被抓,所以先进入正题之前了解下什么 ...

  7. PHP MQTT 实践

    MQTT介绍:http://mqtt.org 服务器端https://mosquitto.org/download/ PHP客户端https://github.com/bluerhinos/phpMQ ...

  8. php nl2br 将\n变成<br />

    <?php $str="h t m l"; //定义一个多处换行的字串 echo "未处理前的输出形式:<br />{$str}"; #nl2 ...

  9. zxing解析生成一维码二维码

    @web界面实现扫一扫 二维码工具类 package util; import java.awt.BasicStroke; import java.awt.Graphics; import java. ...

  10. DL/T645-2007 及 Modbus-RTU智能电表调试工具

    最近在做微信门禁,智能电表采集,智能水表采集项目时,发现网上很多调试工具用起来不顺手,电脑里面起码下了五六个软件. 好不容易弄清楚了,就自己写一个工具,方便自己使用,也顺便造福一下其他人吧