Я создаю 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, спасибо за ваш комментарий. Я использую горутины/каналы, чтобы попытаться добиться того же, что и питоны. Любые рекомендации, которые вы должны улучшить, будут оценены.
Ваш код имеет следующее состояние гонки
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
, откуда их считывает вызывающий.
«не дожидаясь завершения анонимной горутины». Это предполагаемое поведение. «таким образом, горутина завершается при закрытии родительской функции». Это не то, как работают горутины. Горутины — это независимые потоки выполнения.
Возможно, я неправильно понимаю, но если вы запустите тестовый пример, который я пометил как «с тестовым условием», как только основная функция вернет, порожденная горутина будет завершена. Я запустил его во время просмотра htop, и вы могли видеть, что когда один завершается, другой тоже.
@julianmentasti все горутины заканчиваются, когда main возвращается, но пока main не возвращается, все go-процедуры выполняются в независимых потоках, не заботясь о том, кто их запустил
Мне удалось решить мою проблему, очистив карту 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
}
Я также смог удалить блокировки, разбросанные по всему коду, и теперь он возвращает правильную длину хостов при каждом запуске.
Почему это вообще использует горутины/каналы? Он фактически однопоточный, поэтому вы просто добавляете кучу сложности и неопределенности, то есть возможности для странных дефектов, подобных тому, что вы видите.