我用Go编写了一个长期运行的服务器。 Main触发了程序逻辑执行的几个goroutine。之后主要没有任何用处。一旦主要退出,程序将退出。我现在用来保持程序运行的方法只是对fmt.Scanln()的简单调用。我想知道别人如何保持主力退出。以下是一个基本的例子。这里可以使用哪些想法或最佳实践?
我考虑通过在所述频道上接收来创建一个频道并推迟主要的退出,但我认为如果我的所有goroutine在某些时候变得不活跃,那么这可能会有问题。
旁注:在我的服务器(不是示例)中,程序实际上并没有运行连接到shell,所以无论如何与控制台交互都没有意义。现在它可行,但我正在寻找“正确”的方式,假设有一个。
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 :(得分:52)
永远阻挡。例如,
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)
}
}
答案 1 :(得分:26)
Go的运行时的当前设计假定程序员负责检测何时终止goroutine以及何时终止程序。程序员需要计算goroutines以及整个程序的终止条件。通过调用os.Exit
或从main()
函数返回,可以正常方式终止程序。
通过立即接收所述频道来创建频道并延迟main()
的退出是阻止main
退出的有效方法。但它并没有解决检测何时终止程序的问题。
如果在main()
函数进入wait-for-all-goroutines-to-terminate循环之前无法计算goroutine的数量,则需要发送增量以便main
函数可以保持跟踪飞行中有多少goroutine:
// 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()
,因此有必要在main()
中创建至少1个goroutine,而后续的goroutine可以在任何部分创建该计划:
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 :(得分:15)
Go的runtime软件包有一个名为runtime.Goexit
的函数,可以完全按照您的意愿执行。
中的示例从主goroutine调用Goexit会终止该goroutine 没有func主要返回。由于func main还没有返回, 该程序继续执行其他goroutines。 如果所有其他goroutine退出,程序崩溃。
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 :(得分:4)
没有人提到signal.Notify(c chan<- os.Signal, sig ...os.Signal)
示例:
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)
}
}
答案 4 :(得分:2)
这是一个永远使用频道的简单块
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)
}
}
答案 5 :(得分:1)
您可以使用Supervisor(http://supervisord.org/)守护进程。你的函数永远只是它运行的一个过程,它将处理你的函数main的一部分。您可以使用管理程序控制界面来启动/关闭/检查您的流程。