How best do I keep a long running Go program, running?
Go's runtime package has a function called runtime.Goexit
that will do exactly what you want.
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.
Example in the 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")
}
Block forever. For example,
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)
}
}
The current design of Go's runtime assumes that the programmer is responsible for detecting when to terminate a goroutine and when to terminate the program. The programmer needs to compute the termination condition for goroutines and also for the entire program. A program can be terminated in a normal way by calling os.Exit
or by returning from the main()
function.
Creating a channel and delaying exit of main()
by immediately receiving on said channel is a valid approach of preventing main
from exiting. But it does not solve the problem of detecting when to terminate the program.
If the number of goroutines cannot be computed before the main()
function enters the wait-for-all-goroutines-to-terminate loop, you need to be sending deltas so that main
function can keep track of how many goroutines are in flight:
// 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
}
An alternative approach is to replace the channel with sync.WaitGroup
. A drawback of this approach is that wg.Add(int)
needs to be called before calling wg.Wait()
, so it is necessary to create at least 1 goroutine in main()
while subsequent goroutines can be created in any part of the program:
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()
}
Nobody mentioned signal.Notify(c chan<- os.Signal, sig ...os.Signal)
Example:
package main
import (
"fmt"
"time"
"os"
"os/signal"
"syscall"
)
func main() {
go forever()
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM)
<-quitChannel
//time for cleanup before exit
fmt.Println("Adios!")
}
func forever() {
for {
fmt.Printf("%v+\n", time.Now())
time.Sleep(time.Second)
}
}