【问题标题】:Understanding a Golang panic理解 Golang 恐慌
【发布时间】:2018-04-26 10:01:00
【问题描述】:

我有一些 Golang 代码,非常间歇性地(每隔几个小时一次)导致恐慌,我需要一些指导来了解如何找到原因。代码是这样的(带行号):

audio-process.go:

    var MyChannel chan<- interface{}
    var newDatagramList = list.New()

    func operateAudioProcessing() {
        var channel = make(chan interface{})
        MyChannel = channel
        newDatagramList.Init()
        ...
415     go func() {
416        for cmd := range channel {
417            switch msg := cmd.(type) {
418                 case *MyThing:
419                 {
420                     newDatagramList.PushBack(msg)
421                 }
422             }
423         }
424     }()
425 }

...当在第 420 行调用 newDatagramList.PushBack() 时发生恐慌。发送到此通道的代码是:

audio-in.go:

    thing := new(MyThing)
    ...
    MyChannel <- thing

...并且,为了完成图片,有一个单独的定时go func() 处理newDatagramList,如下所示:

go func() {
    var next *list.Element
    for _ = range processTicker.C {
        for newElement := newDatagramList.Front(); newElement != nil; newElement = next {
            next = newElement.Next();
            myProcessingFunction(newElement.Value.(*MyThing))
            newDatagramList.Remove(newElement)
        }
}

恐慌输出是:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x5702d1]

goroutine 12 [running]:
panic(0x76e520, 0xc82000e100)
/usr/lib/go-1.6/src/runtime/panic.go:481 +0x3e6
container/list.(*List).PushBack(0xc820054e40, 0x6c4f80, 0xc827294e20, 0xc8273e0b01)
/usr/lib/go-1.6/src/container/list/list.go:139 +0x1c1
main.operateAudioProcessing.func2(0xc8200164e0, 0xc82000f310, 0xc820024078, 0x240)
/home/rob/gocode/src/audio-process.go:420 +0x58b
created by main.operateAudioProcessing
/home/rob/gocode/src/audio-process.go:442 +0x5ba

恐慌告诉我这里有什么问题?首先没有关于分配和发送到频道的投诉,所以我不明白这怎么可能是错误的。 newDatagramList 显然已初始化(并且通道已经运行并接收消息一段时间了)。

我如何确定是什么让我爆炸了?

【问题讨论】:

  • 我认为恐慌是说问题出在 list 包中的 Pushback 方法中。有点怀疑 newDatagramList 是一个全局包。可能是竞争条件,因为列表不是线程安全的 - newDatagramList 是从另一个 goroutine 访问的吗?
  • 您可以使用go run -race 测试您的应用,看看您是否有数据竞争。
  • golang.org/src/container/list/list.go?s=3579:3626#L128 在 `container/list List 上看起来根本没有任何同步
  • 我想知道go func() 循环是否可能会尝试处理和删除添加了一半的项目?
  • 我很确定根本原因是 container/list 访问不是线程安全的,Remove()PushBack/Front/Next 由于访问不同步而创建了竞争条件 @987654322 @github.com/golang/go/blob/release-branch.go1.6/src/container/…

标签: go


【解决方案1】:

恐慌堆栈:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x5702d1]

goroutine 12 [running]:
panic(0x76e520, 0xc82000e100)
/usr/lib/go-1.6/src/runtime/panic.go:481 +0x3e6
container/list.(*List).PushBack(0xc820054e40, 0x6c4f80, 0xc827294e20, 0xc8273e0b01)
/usr/lib/go-1.6/src/container/list/list.go:139 +0x1c1
main.operateAudioProcessing.func2(0xc8200164e0, 0xc82000f310, 0xc820024078, 0x240)
/home/rob/gocode/src/audio-process.go:420 +0x58b
created by main.operateAudioProcessing
/home/rob/gocode/src/audio-process.go:442 +0x5ba

表示“无效的内存地址或 nil 指针取消引用”发生在顶部列出的帧中

 container/list.(*List).PushBack(0xc820054e40, 0x6c4f80, 0xc827294e20, 0xc8273e0b01)

开启

 /usr/lib/go-1.6/src/container/list/list.go:139 +0x1c1

看起来是from godoc

// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List) PushBack(v interface{}) *Element {
    l.lazyInit()
    return l.insertValue(v, l.root.prev)
}

这很酷,因为它在这个框架中,它看起来在l.root.prev 上出错

container/list 上构建的数据类型是完全不同步的,并且存在许多 go 例程的交错,这可能会导致同时删除列表元素指针的中间状态。

例如remove有很多不同的操作:

func (l *List) remove(e *Element) *Element {
    e.prev.next = e.next
    e.next.prev = e.prev
    e.next = nil // avoid memory leaks
    e.prev = nil // avoid memory leaks
    e.list = nil
    l.len--
    return e
}

如果这是同步执行的,则不会有问题,但因为有多个 go 例程在其上运行。

假设我们有一个元素E,有一个指向前一个和一个下一个的指针,以及两个作用于它的goroutine

PREV - E - NEXT  

GOROUTINE1                                           GOROUTINE2

READ - E.PREV returns element (E2) with `NEXT` -> E

                                                    REMOVE(E) is called
                                                    e.next = nil // avoid memory leaks

E2.NEXT.NEXT access occurs now Nil!!! 
resulting in panic 

【讨论】:

  • 我想这告诉我们一些关于 Golang 如何实现通道和go func():问题的发生PushBack() 必须在Remove() 中间发生(我认为),所以这些不是愚蠢的循环,它是正确的线程。数据每 20 毫秒到达一次,go func() 进程循环每 20 毫秒运行一次,但问题发生需要一两个小时(数百万个事件)。无论如何,我希望互斥锁能解决它。可能 Golang 人应该声明 container/list 不是线程安全的,作为对其他人的警告。感谢大家的帮助!
  • @Rob !!!是的!!我在他们的 godoc 中搜索线程安全。我很惊讶他们也没有明确提及它
  • 我在 Github 上为他们添加了一个文档问题。
  • ...here 但他们认为没有必要,因为大多数 Golang 的类 C 函数同样不是线程安全的。只需要记住,在很多方面,Golang 都是 good-ole C 前面的解析器。这是一种力量,千万不能忘记。
  • 确认在sync.Lock()/Unlock()保护下运行3小时后问题没有再次出现。
猜你喜欢
  • 2015-03-29
  • 1970-01-01
  • 2015-09-28
  • 1970-01-01
  • 2014-09-21
  • 2014-10-10
  • 2021-07-11
  • 2020-10-23
  • 1970-01-01
相关资源
最近更新 更多