Как лучше сохранить работающую долго программу Go? - PullRequest
38 голосов
/ 03 марта 2012

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

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

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

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)
    }
}

Ответы [ 5 ]

53 голосов
/ 03 марта 2012

Блок навсегда. Например,

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)
    }
}
26 голосов
/ 03 марта 2012

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

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

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

// 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 подпрограмму в main(), тогда как последующие подпрограммы могут быть созданы в любой части программы:

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()
}
17 голосов
/ 09 августа 2016

Go's runtime пакет имеет функцию с именем runtime.Goexit, которая будет делать именно то, что вы хотите.Горутин без возврата основного функционала.Поскольку функция func main не вернулась, программа продолжает выполнение других программ.Если все другие программы завершаются, программа вылетает.

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

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")
}
3 голосов
/ 02 августа 2013

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

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)
    }
}
1 голос
/ 03 марта 2012

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

...