Будет ли этот код с небуферизованным каналом вызывать утечку памяти в Go? - PullRequest
0 голосов
/ 28 мая 2019

Я пишу несколько кодов параллелизма golang с goroutines и каналами, подозревая, что мой код может вызвать утечки goroutine. Моя ситуация похожа на следующий код, или вы можете открыть эту ссылку go playstation .

func main() {
    numCount := 3
    numChan := make(chan int)

    for i := 0; i < numCount; i++ {
        go func(num int) {
            fmt.Printf("Adding num: %d to chan\n", num)
            numChan <- num
            fmt.Printf("Adding num: %d to chan Done\n", num)
        }(i)
    }

    time.Sleep(time.Second)
    panic("Goroutine Resource Leak Test")
} 

По моему мнению, когда основная программа возвращается, три программы блокируются, отправляя их на небуферизованный канал, и происходит утечка программы. Этот пост утечка goroutine с буферизованным каналом в Go также предполагает, что So only if the channel was unbuffered the leak would occur

Язык программирования Go предполагает, что:

Есть полезный прием, который мы можем использовать во время тестирования: если вместо возврата из main в случае отмены мы выполним вызов паники, тогда среда выполнения сбросит стек каждой программы в программе. Если основная программа оставлена ​​только одна, то она убрала за собой. Но если другие программы остаются, они, возможно, не были должным образом отменены, или, возможно, они были отменены, но отмена требует времени; небольшое расследование может стоить. Панический дамп часто содержит достаточную информацию, чтобы различать эти случаи.

Поэтому я добавил panic("Goroutine Resource Leak Test") в конец основной функции, чтобы проверить мое предположение. Однако дамп паники содержит только основные процедуры, то есть утечки ресурсов нет.

Adding num: 0 to chan
Adding num: 1 to chan
Adding num: 2 to chan
panic: Goroutine Resource Leak Test

goroutine 1 [running]:
main.main()
    /tmp/sandbox011109649/prog.go:21 +0xc0

Может кто-нибудь помочь объяснить

  • почему нет утечки горутина или
  • как мне получить правильный панический дамп, если есть утечка

Любое предложение будет оценено, заранее спасибо!

1 Ответ

4 голосов
/ 28 мая 2019

Проблема с вашим кодом двоякая.

Во-первых, теоретически - утечка маршрутной программы, поскольку любая попытка отправить значение в канал с нулевой пропускной способностью (небуферизованный канал или заполненный буферизованный канал) блокирует отправляющую программу до получения операция выполняется на этом канале.

Итак, да, по определению того, как работают каналы, все ваши три процедуры будут заблокированы в операторе numChan <- num.

Во-вторых, начиная с некоторой ревизии Go, необработанный panic по умолчанию сбрасывает только след стека из программы паники. Если вы хотите сбросить стеки всех активных подпрограмм, вам нужно настроить время выполнения - из документации пакета runtime:

Переменная GOTRACEBACK управляет объемом выходных данных, генерируемых в случае сбоя программы Go из-за невосстановленной паники или неожиданного состояния во время выполнения. По умолчанию ошибка печатает трассировку стека для текущей программы, исключая внутренние функции системы выполнения, а затем завершает работу с кодом выхода 2. Ошибка печатает трассировки стека для всех программ, если текущая программа не работает или происходит сбой. внутренний для времени выполнения. GOTRACEBACK=none полностью исключает следы стека goroutine. GOTRACEBACK=single (по умолчанию) ведет себя так, как описано выше. GOTRACEBACK=all добавляет трассировки стека для всех пользовательских процедур. GOTRACEBACK=system похож на «все», но добавляет кадры стека для функций времени выполнения и отображает процедуры, созданные внутри во время выполнения. GOTRACEBACK=crash походит на «систему», но вылетает в зависимости от операционной системы вместо выхода. Например, в системах Unix сбой вызывает SIGABRT, чтобы вызвать дамп ядра. По историческим причинам настройки GOTRACEBACK 0, 1 и 2 являются синонимами для none, all и system соответственно. Функция The runtime/debug пакета SetTraceback позволяет увеличить объем выходных данных во время выполнения, но не может уменьшить объем ниже значения, указанного в переменной среды. См https://golang.org/pkg/runtime/debug/#SetTraceback.


Также обратите внимание, что вы не должны когда-либо использовать таймеры для (имитации) синхронизации: в примере с игрушкой это может сработать, но в реальной жизни ничто не препятствует тому, чтобы у ваших трех подпрограмм не было шанс на запуск в течение промежутка времени, в течение которого ваша основная программа была потрачена на вызов time.Sleep, поэтому в результате может быть запущено любое количество порожденных программ: от 0 до 3.

Добавьте сюда тот факт, что когда main выходит из среды выполнения, он просто убивает все выдающиеся активные программы, и результат теста может быть в лучшем случае неожиданным.

Следовательно, правильным решением было бы

  • Просто напечатайте стеки там, где нужно,
  • Удостоверьтесь, что вы синхронизируете посылки с совпадающими получениями:
package main

import (
    "fmt"
    "log"
    "runtime"
)

func dumpStacks() {
    buf := make([]byte, 32 * 1024)
    n := runtime.Stack(buf, true)
    log.Println(string(buf[:n]))
}

func main() {
    numCount := 3
    numChan := make(chan int, numCount)

    for i := 0; i < numCount; i++ {
        go func(num int) {
            fmt.Printf("Adding num: %d to chan\n", num)
            numChan <- num
            fmt.Printf("Adding num: %d to chan Done\n", num)
        }(i)
    }

    dumpStacks()

    for i := 0; i < numCount; i++ {
        <-numChan
    }
}

Детская площадка .

...