Проблема с выполнением пакета fmt

Я столкнулся с проблемой, которая кажется простой, но которую я не могу воспроизвести и поэтому не могу объяснить.

Эта проблема возникает на производстве, что загадочно, так это то, что она возникает очень редко (поэтому я не могу ее воспроизвести), это может быть фактор, который я не мог привести в качестве примера, но вот контекст:

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

Спасибо за ваше время, я надеюсь, что кто-то может объяснить мне, что не так.


person Anarz    schedule 20.01.2020    source источник
comment
Назначение *Получить гонки против условия for сразу после этого. Условия гонки делают поведение вашей программы неопределенным.   -  person Peter    schedule 20.01.2020


Ответы (1)


У вас тут гонка данных:

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

Горутина, которая записывает в переменную Object, делает это без использования блокировки: она получает из канала и, когда получает значение, записывает его в *Get, где Get == &Object, так что она записывает значение интерфейса Object.

Тем временем горутина, выполняющая цикл for, читает из Object, проверяя наличие нулей. Он читает без использования блокировки, так что он может прочитать частично записанное значение.

На самом деле происходит то, что частично записанное значение не равно нулю, без установки всего значения. Таким образом, цикл for останавливается, и код переходит к следующей строке:

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

Поскольку Object записано частично, доступ к его значению приводит к непредсказуемым результатам, но в данном случае вызывает панику. (В частности, поле type интерфейса было установлено, но поле value по-прежнему равно нулю. Фактическая паника из этой строки в src/reflect/value.go:

               return *(*string)(v.ptr)

при этом v.ptr не был установлен.)

Непонятно, почему вы вообще записываете это значение или используете общую память для связи, но если вы делаете это, вам потребуется блокировка. Обычно разумнее этого не делать. См. также этот ответ на Объяснить : Не общайтесь, разделяя память; поделиться памятью, общаясь.

(Или, проще говоря, почему бы просто не использовать Object := <-MyChan вместо всей горутины и вращения?)

person torek    schedule 20.01.2020
comment
Между горутиной и for Object == nil {} есть кое-что, но на самом деле вы выявили ошибку, которая требует полного пересмотра этого метода. Я последую вашему совету по обмену памятью. - person Anarz; 20.01.2020