2012-03-03 9 views
29

Mam już działający serwer napisany w Go. Główny odpala kilka goroutin, w których realizuje się logika programu. Po tym głównym nie robi nic użytecznego. Po wyjściu głównym program zostanie zamknięty. Metoda, której teraz używam, aby program działał, to po prostu proste wywołanie funkcji fmt.Scanln(). Chciałbym wiedzieć, jak inni trzymają się z dala od wyjścia. Poniżej znajduje się podstawowy przykład. Jakie pomysły lub najlepsze praktyki można tu zastosować?Jak najlepiej utrzymywać długi program Go, działający?

Rozważałem utworzenie kanału i opóźnienie wyjścia z głównego przez odbieranie na tym kanale, ale myślę, że może to być problematyczne, jeśli wszystkie moje goryle staną się nieaktywne w pewnym momencie.

Nota boczna: Na moim serwerze (nie w przykładzie) program nie jest faktycznie podłączony do powłoki, więc nie ma sensu wchodzić w interakcję z konsolą. Na razie działa, ale szukam "prawidłowego" sposobu, zakładając, że istnieje.

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

Znalezionych 6 sposobów, aby to - http: // pliutau.com/different-ways-to-block-go-runtime-forever/ – pltvs

Odpowiedz

18

Obecny projekt środowiska wykonawczego Go zakłada, że ​​programista jest odpowiedzialny za wykrycie, kiedy należy zakończyć goroutine i kiedy zakończyć program. Programista musi wyliczyć warunki zakończenia dla goroutinów, a także dla całego programu. Program można zakończyć w normalny sposób, wywołując os.Exit lub wracając z funkcji main().

Utworzenie kanału i opóźnienie wyjścia z numeru main() przez natychmiastowe odebranie na tym kanale to prawidłowe podejście polegające na uniemożliwieniu wyjścia z telefonu main. Ale nie rozwiązuje problemu wykrycia, kiedy zakończyć program.

Jeżeli liczba goroutines nie mogą być obliczane przed funkcją main() wchodzi w pętlę wait-for-all-goroutines-do-wypowiedzenia, trzeba wysyłać delty tak że main funkcji można śledzić ile goroutines są w lot:

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

alternatywnym rozwiązaniem jest zastąpienie kanał z sync.WaitGroup. Wadą tego podejścia jest to, że wg.Add(int) musi zostać wywołana przed wywołaniem wg.Wait(), więc konieczne jest utworzenie co najmniej 1 goroutine w main() podczas kolejnych goroutines mogą być tworzone w dowolnej części programu:

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() 
} 
+0

Dokładna odpowiedź i świetne wyjaśnienie. Prawdopodobnie pójdę z odbiorem kanału, ponieważ moje procedury rutowania są pochowane w innych pakietach, które obsługują. Nie chcę robić goroutine wszędzie. Kanał będzie działał dobrze, jeśli zdecyduję się dodać logikę wyłączania do programu za pomocą sygnałów lub innego IPC ... Dzięki! – Nate

1

Można daemonize proces używając Supervisor (http://supervisord.org/). Twoja funkcja na zawsze byłaby po prostu procesem, który będzie działał, i poradziłaby sobie z częścią głównej funkcji. Do uruchamiania/zamykania/sprawdzania procesu używałbyś interfejsu sterowania nadzorcy.

+0

Ciekawy system, ale wygląda na to, że jest to rzeczywisty program, a nie kod Go, którego użyłbym. Zamierzam edytować moje pytanie, ponieważ staram się wymyślić najlepsze praktyki, aby zachować główne wyjście. Teraz używam fmt.Scanln(), chcę wiedzieć, czy jest lepszy sposób ... Ale dzięki za wskazówkę do inspektora. Widziałem, jak badamy to dalej. – Nate

36

bloku na zawsze . Na przykład,

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

Proste. Pobiera zadanie. Awansuj. – Nate

+0

to podejście nie wydaje się już działać w najnowszym go, rzuca po błędzie 'goroutine 1 [wybierz (żadnych przypadków)]: main.main()' –

+1

@dark_ruby: Działa na najnowszej wersji Go: 'go wersja devel + 13c44d2 Sat cze 20 10:35:38 2015 +0000 linux/amd64'. Jak możemy dokładnie odtworzyć twoją porażkę? Bądź bardzo precyzyjny, na przykład "najnowsze wykonanie" jest zbyt ogólnikowe. – peterSO

1

Oto prosty blok zawsze za pomocą kanałów

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

runtime pakiet GO posiada funkcję o nazwie runtime.Goexit że zrobi dokładnie to, co chcesz.

Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.

przykład w playground

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") 
} 
+0

To jest bardzo fajne. Ale, muszę się zastanowić, dlaczego zdecydowali się na awarię programu zamiast go z wdziękiem wyjść. Czy pomysł, abyś miał kolejną gorutynę obsługującą zamknięcie aplikacji i wywoła wyjście, gdy otrzyma sygnał? Zastanawiam się, w jaki sposób poprawnie użyć tej funkcji. – Nate

+1

@Nate, które jest poprawne. Na przykład sprawdź ten [kod] (https://play.golang.org/p/8N5Yr3ZNPT), procedura Go 2 kończy program, a program się nie psuje. Jeśli Goexit zawiesza twój program, to dlatego, że wszystkie inne procedury Go zostały zakończone. Bardzo podoba mi się, jak to się dzieje, powiedzmy, że nic nie robi, w przeciwieństwie do 'select {}', które blokowałoby się na zawsze, nawet gdyby nic się nie działo. – jmaloney

+0

To wymaga więcej upvotes. –

Powiązane problemy