Подтвердить что ты не робот

Как лучше всего работать с программой Go, работающей?

У меня длинный сервер, написанный на Go. Main запускает несколько goroutines, где выполняется логика программы. После этого главное ничего не делает. Как только основные выходы, программа прекратит работу. Метод, который я использую сейчас, чтобы поддерживать работу программы, - это просто вызов fmt.Scanln(). Я хотел бы знать, как другие держат основную часть от выхода. Ниже приведен базовый пример. Какие идеи или рекомендации могут быть использованы здесь?

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

Боковое примечание: на моем сервере (а не в примере) программа фактически не подключена к оболочке, поэтому на самом деле не имеет смысла взаимодействовать с консолью. Пока это работает, но я ищу "правильный" способ, предполагая, что он есть.

package main

import (
    "fmt"
    "time"
)

func main() {
    go forever()
    //Keep this goroutine from exiting
    //so that the program doesn't end.
    //This is the focus of my question.
    fmt.Scanln()
}

func forever() {
    for ; ; {
    //An example goroutine that might run
    //indefinitely. In actual implementation
    //it might block on a chanel receive instead
    //of time.Sleep for example.
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}
4b9b3361

Ответ 1

Нынешняя конструкция Go runtime предполагает, что программист несет ответственность за обнаружение, когда прекращать работу goroutine и когда прекращать работу программы. Программисту необходимо вычислить условие завершения для goroutines, а также для всей программы. Программа может быть завершена обычным способом, вызвав os.Exit или возвращаясь из функции main().

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

Если число goroutines не может быть вычислено до того, как функция main() войдет в цикл ожидания для всех-goroutines-to-terminate, вам нужно отправлять дельта, чтобы функция main могла отслеживать, сколько goroutines находятся в полете:

// Receives the change in the number of goroutines
var goroutineDelta = make(chan int)

func main() {
    go forever()

    numGoroutines := 0
    for diff := range goroutineDelta {
        numGoroutines += diff
        if numGoroutines == 0 { os.Exit(0) }
    }
}

// Conceptual code
func forever() {
    for {
        if needToCreateANewGoroutine {
            // Make sure to do this before "go f()", not within f()
            goroutineDelta <- +1

            go f()
        }
    }
}

func f() {
    // When the termination condition for this goroutine is detected, do:
    goroutineDelta <- -1
}

Альтернативный подход заключается в замене канала sync.WaitGroup. Недостатком этого подхода является то, что wg.Add(int) нужно вызвать перед вызовом wg.Wait(), поэтому необходимо создать не менее 1 goroutine в main(), в то время как последующие goroutines могут быть созданы в любой части программы:

var wg sync.WaitGroup

func main() {
    // Create at least 1 goroutine
    wg.Add(1)
    go f()

    go forever()
    wg.Wait()
}

// Conceptual code
func forever() {
    for {
        if needToCreateANewGoroutine {
            wg.Add(1)
            go f()
        }
    }
}

func f() {
    // When the termination condition for this goroutine is detected, do:
    wg.Done()
}

Ответ 2

Навсегда. Например,

package main

import (
    "fmt"
    "time"
)

func main() {
    go forever()
    select {} // block forever
}

func forever() {
    for {
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}

Ответ 3

Go runtime пакет имеет функцию runtime.Goexit, который будет делать именно то, что вы хотите.

Вызов Goexit из главного горута заканчивается тем, что goroutine   без func main return. Поскольку func main не вернулся,   программа продолжает выполнять другие горуты.   Если все остальные goroutines выходят, программа выйдет из строя.

Пример на игровая площадка

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    go func() {
        time.Sleep(time.Second)
        fmt.Println("Go 1")
    }()
    go func() {
        time.Sleep(time.Second * 2)
        fmt.Println("Go 2")
    }()

    runtime.Goexit()

    fmt.Println("Exit")
}

Ответ 4

Вы можете демонизировать процесс, используя Supervisor (http://supervisord.org/). Ваша функция навсегда будет просто процессом, который он запускает, и он будет обрабатывать часть вашей основной функции. Вы должны использовать интерфейс управления супервизора для запуска/выключения/проверки вашего процесса.

Ответ 5

Вот простой блок навсегда, используя каналы

package main

import (
    "fmt"
    "time"
)

func main() {
    done := make(chan bool)
    go forever()
    <-done // Block forever
}

func forever() {
    for {
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}