【问题标题】:golang: how to debug possible race conditiongolang:如何调试可能的竞争条件
【发布时间】:2017-01-12 13:52:44
【问题描述】:

我在go中写了一个日志收集程序,它运行了一堆goroutines如下:

  1. 例程A运行HTTP服务器,允许用户查看日志信息
  2. 例程 B 运行 UDP 服务器,允许从 LAN 向其发送日志消息
  3. 例程 C 运行一个计时器,它定期从内部 HTTP 文件服务器(不是程序的一部分)查询/下载压缩日志档案
  4. 例程 B 和 C 都将处理后的消息发送到 Channel
  5. 例程 D 运行一个带有 select 语句的 for {} 循环,该语句从 Channel 接收消息并将其刷新到磁盘
  6. 还有一些其他的 go 例程,例如扫描例程 D 生成的日志存档以创建 SQLite 索引等的例程。

程序有一个问题,在运行几个小时后,日志查看器 http 服务器仍然可以正常工作,但是没有来自 UDP 或文件服务器例程的消息。我知道从各种渠道发送的日志消息无穷无尽,如果我重新启动程序,它会再次开始处理传入的日志。

我在编译器中添加了-race,它确实发现了一些有问题的代码,我修复了这些,但问题仍然存在。更重要的是,虽然存在不雅问题,但在我们的生产服务器上运行的旧版本代码运行良好,不管不雅代码。

我的问题是,我该如何继续查明问题。以下是我的日志处理例程中的关键循环:

for {
    select {
    case msg := <-logCh:
        logque.Cache(msg)
    case <-time.After(time.Second):
    }
    if time.Since(lastFlush) >= 3 * time.Second {
        logque.Flush()
        lastFlush = time.Now()
    }
}

【问题讨论】:

  • 查看服务器停止工作时的堆栈跟踪,并查看每个 goroutine 被阻塞的位置。
  • 程序运行良好时如何查看堆栈跟踪?请注意,显然没有任何问题,只是定时器例程在触发时没有打印消息,并且 UDP 服务器停止接收消息,并且控制台上没有任何输出
  • 您可以发送 SIGQUIT 以退出堆栈跟踪,设置 http 处理程序或信号处理程序以按需打印,使用 pprof goroutine 端点等。您的 goruotines 因某种原因被阻止,所以你想知道为什么。
  • 另外,如果有办法查看实时 goroutine 的跟踪,我该如何指出问题?即从堆栈跟踪中,什么表示阻塞问题?跟踪将明确告诉我例程处于“阻塞”状态?

标签: go race-condition


【解决方案1】:

我终于找到了造成阻塞的代码。在以下代码中:

for {
    select {
    case msg := <-logCh:
        logque.Cache(msg)
    case <-time.After(time.Second):
    }
    if time.Since(lastFlush) >= 3 * time.Second {
        logque.Flush()
        lastFlush = time.Now()
    }
}

在 logque.Flush() 内部有一些代码会生成日志消息,这些消息又会写入通道,最终导致通道的缓冲区被填满。这仅在我打开调试模式时发生,生产代码不会在 Flush() 方法中执行此操作。

为了回答我自己的问题,我用来确定问题的方法很简单:

if len(logch) >= LOG_CHANNEL_CAP {
    //drop the message or store it into
    //secondary buffer...
    return
}
logch <- msg

【讨论】:

  • 虽然问题解决了,但是我还是很感兴趣如何以黑盒的方式找出阻塞的原因。正如@JimB 建议的那样,打破一段正在运行的代码,并检查它的堆栈跟踪信息,这可能吗?
猜你喜欢
  • 1970-01-01
  • 2023-04-06
  • 2013-04-13
  • 2021-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-17
相关资源
最近更新 更多