【问题标题】:Package "fmt" runtime issue包“fmt”运行时问题
【发布时间】:2020-05-06 07:07:36
【问题描述】:

我遇到了一个看似简单但我无法重现的问题,因此我无法解释。

这个问题在生产中出现,神秘的是它很少发生(这就是为什么我无法重现它),这可能是我无法举例说明的一个因素,但这里是上下文:

type MyType struct {
    Field1 string
    Field2 int
    Field3 time.Time
    Field4 []float64
    // No pointers fields
}

func main() {
    var MyChan = make(chan interface{})

    go func() {
// This routine is reading and parsing messages from a WS dial and writing it in a channel
// There is 2 only possible answer in the channel : a string, or a "MyType" like (with more fields, but no pointers in)     
        var Object MyType
        Object.Field1 = "test"
        // ..

        MyChan <- Object
    }()

    go func() {
// This routine is sending a message to a WS dial and wait for the answer in a channel fed by another routine :
        var Object interface{}
        go func(Get *interface{}) {
            *Get = <- MyChan
        } (&Object)
        for Object == nil {
            time.Sleep(time.Nanosecond * 1)
        }
        log.Println(fmt.Sprint(Object)) // Panic here from the fmt.Sprint() func
    }()
}

紧急堆栈跟踪:

runtime error: invalid memory address or nil pointer dereference
panic(0x87a840, 0xd0ff40)
    X:/Go/GoRoot/src/runtime/panic.go:522 +0x1b5
reflect.Value.String(0x85a060, 0x0, 0xb8, 0xc00001e500, 0xc0006f1a80)
    X:/Go/GoRoot/src/reflect/value.go:1781 +0x45
fmt.(*pp).printValue(0xc006410f00, 0x85a060, 0x0, 0xb8, 0x76, 0x1)
    X:/Go/GoRoot/src/fmt/print.go:747 +0x21c3
fmt.(*pp).printValue(0xc006410f00, 0x8ed5c0, 0x0, 0x99, 0x76, 0x0)
    X:/Go/GoRoot/src/fmt/print.go:796 +0x1b52
fmt.(*pp).printArg(0xc006410f00, 0x8ed5c0, 0x0, 0x76)
    X:/Go/GoRoot/src/fmt/print.go:702 +0x2ba
fmt.(*pp).doPrint(0xc006410f00, 0xc0006f22a0, 0x1, 0x1)
    X:/Go/GoRoot/src/fmt/print.go:1147 +0xfd
fmt.Sprint(0xc0006f22a0, 0x1, 0x1, 0x0, 0x0)
    X:/Go/GoRoot/src/fmt/print.go:250 +0x52

Go 版本:1.12.1 windows/amd64

感谢您抽出宝贵时间,希望有人能向我解释问题所在。

【问题讨论】:

  • 分配给 *Get 之后立即针对 for 条件进行比赛。竞争条件使您的程序的行为未定义。

标签: go runtime panic


【解决方案1】:

你在这里有一个数据竞赛:

    var Object interface{}
    go func(Get *interface{}) {
        *Get = <- MyChan
    } (&Object)
    for Object == nil {
        time.Sleep(time.Nanosecond * 1)
    }

写变量Object的goroutine不使用锁:它从通道接收,当它得到一个值时,它把它写到*GetGet == &amp;Object,所以它写Object的接口值。

同时,运行for 循环的goroutine 从Object 读取数据,检查是否为零。它读取时不使用锁,因此它可以读取部分写入的值。

实际发生的情况是部分写入的值不是 nil,而没有设置整个值。所以for 循环停止循环,代码进入下一行:

    log.Println(fmt.Sprint(Object)) // Panic here from the fmt.Sprint() func

由于Object 只是部分写入,访问它的值会产生不可预知的结果——但在这种情况下,它会产生恐慌。 (特别是接口的type字段已经设置,但是value字段还是0。实际panic来自src/reflect/value.go中的这一行:

               return *(*string)(v.ptr)

v.ptr 尚未设置。)

不清楚为什么要记录值,也不清楚为什么要使用共享内存进行通信,但如果你这样做这样做,你将需要锁定。不这样做通常更明智。另请参阅 this answerExplain: Don't communicate by sharing memory; share memory by communicating

(或者,更简单地说,为什么不直接使用 Object := &lt;-MyChan 来代替整个 goroutine-and-spin?)

【讨论】:

  • goroutine 和 for Object == nil {} 之间有一些东西,但实际上,你暴露了一个错误,这意味着要彻底检查这个方法。我会听从你关于内存共享的建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
  • 2014-06-27
  • 2021-08-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多