Карта карт разного размера при разборе данных из файла

Я создаю API для Nagios, вдохновленный этот проект. Я начал воссоздавать код, который читает файл status.dat и сохраняет данные в ряде объектов, которые затем используются для создания хостов, служб, информационных словарей, которые находятся в файле core.py.

Ниже моя версия кода Python для Go, которая, похоже, работает так, как ожидалось. Он все еще находится на ранней стадии, поэтому я прошу прощения за любую неправильную практику кодирования.

var mu = &sync.RWMutex{}

func openStatusFile() *os.File {
    file, err := os.Open("/usr/local/nagios/var/status.dat")
    if err != nil {
    }
    return file
}

func nextStanza() <-chan map[string]string {

    myChannel := make(chan map[string]string)

    scanner := bufio.NewScanner(openStatusFile())

    current := make(map[string]string)

    go func() {
        for scanner.Scan() {
            mainline := scanner.Text()
            line := strings.TrimSpace(mainline)
            if strings.HasSuffix(line, "{") {
                if len(current) != 0 {
                    myChannel <- current
                }
                result := strings.SplitN(line, " ", 2)
                mu.Lock()
                current["type"] = result[0]
                mu.Unlock()
            } else if strings.Contains(line, " = ") {
                result := strings.SplitN(line, " = ", 2)
                key := result[0]
                val := result[1]
                mu.Lock()
                current[key] = val
                mu.Unlock()
            }
        }
        close(myChannel)
    }()
    return myChannel
}

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

func main() {

    hoststatus := nextStanza()

    hosts := make(map[string]map[string]string)
    // services := make(map[string]map[string]map[string]string)
    var host string
    // var service string

    for obj := range hoststatus {
        var hostPlaceHolder string
        var typePlaceHolder string

        mu.Lock()
        hostPlaceHolder = obj["host_name"]
        mu.Unlock()

        if hostPlaceHolder != "" {
            host = hostPlaceHolder
        }

        mu.Lock()
        typePlaceHolder = obj["type"]
        mu.Unlock()

        if typePlaceHolder == "hoststatus" {
            mu.Lock()
            hosts[host] = obj
            mu.Unlock()
        }
    }
    fmt.Println(len(hosts))
}

Первый забег:

$ go run -race mytest.go
93

Второй запуск:

$ go run -race mytest.go
95

Третий запуск:

$ go run -race mytest.go
63

Вы поняли идею.

Я чувствую, что проблема связана с картой, потому что, если я просто распечатаю хосты, не помещая их в карту, я увижу все хосты, которые я ожидаю. Что может быть причиной того, что карта будет разного размера при каждом запуске?

Почему это вообще использует горутины/каналы? Он фактически однопоточный, поэтому вы просто добавляете кучу сложности и неопределенности, то есть возможности для странных дефектов, подобных тому, что вы видите.

Adrian 26.07.2019 18:08

Привет @Adrian, спасибо за ваш комментарий. Я использую горутины/каналы, чтобы попытаться добиться того же, что и питоны. Любые рекомендации, которые вы должны улучшить, будут оценены.

markybb41 26.07.2019 18:18
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
2
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваш код имеет следующее состояние гонки

func nextStanza() <-chan map[string]string {

    myChannel := make(chan map[string]string)

    scanner := bufio.NewScanner(openStatusFile())

    current := make(map[string]string)

    go func() {
        for scanner.Scan() {
            mainline := scanner.Text()
            line := strings.TrimSpace(mainline)
            if strings.HasSuffix(line, "{") {
                if len(current) != 0 {
                    myChannel <- current
                }
                result := strings.SplitN(line, " ", 2)
                mu.Lock()
                current["type"] = result[0]
                mu.Unlock()
            } else if strings.Contains(line, " = ") {
                result := strings.SplitN(line, " = ", 2)
                key := result[0]
                val := result[1]
                mu.Lock()
                current[key] = val
                mu.Unlock()
            }
        }
        close(myChannel)
    }()
    return myChannel
}

Когда вы запускаете горутину для анонимной функции, вы не создаете для нее группу ожидания. Это означает, что функция nextStanza() собирается инициировать горутину, а затем return, не дожидаясь завершения анонимной горутины, таким образом завершая горутину при закрытии родительской функции.

Я бы предложил использовать группу ожидания, таким образом вы можете гарантировать, что анонимная функция завершится.

Простой пример, иллюстрирующий происходящее:

С условием гонки

import (
    "fmt"
    "time"
    // "sync"
    )

func main() {
    go func() {
        time.Sleep(3 * time.Second)
        fmt.Println("hai")
    }()
    return
}

Без условия гонки

import (
    "fmt"
    "time"
    "sync"
    )

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        time.Sleep(3 * time.Second)
        fmt.Println("hai")
        wg.Done()
    }()
    wg.Wait()
    return
}

Это не состояние гонки. В данном случае это даже не логическая проблема (он продолжает работать и возвращает результаты на myChannel, откуда их считывает вызывающий.

Adrian 26.07.2019 18:19

«не дожидаясь завершения анонимной горутины». Это предполагаемое поведение. «таким образом, горутина завершается при закрытии родительской функции». Это не то, как работают горутины. Горутины — это независимые потоки выполнения.

Peter 26.07.2019 19:07

Возможно, я неправильно понимаю, но если вы запустите тестовый пример, который я пометил как «с тестовым условием», как только основная функция вернет, порожденная горутина будет завершена. Я запустил его во время просмотра htop, и вы могли видеть, что когда один завершается, другой тоже.

julian mentasti 26.07.2019 19:36

@julianmentasti все горутины заканчиваются, когда main возвращается, но пока main не возвращается, все go-процедуры выполняются в независимых потоках, не заботясь о том, кто их запустил

Pizza lord 29.07.2019 15:52
Ответ принят как подходящий

Мне удалось решить мою проблему, очистив карту current после ее отправки на канал.

myChannel <- current
current = make(map[string]string)

Затем в функции main() после цикла for obj := range hoststatus я поместил эти данные в отдельную карту, чтобы затем работать с ними.

hostStatusMap := make(map[string]string)
    for k, v := range obj {
        hostStatusMap[k] = v
    }

Я также смог удалить блокировки, разбросанные по всему коду, и теперь он возвращает правильную длину хостов при каждом запуске.

Другие вопросы по теме