如何保持长时间运行的Go程序,运行?
我用Go编写了一个长时间运行的服务器。主程序会执行程序逻辑的几个例程。之后,主没有任何用处。一旦主要退出,程序将退出。我现在用来保持程序运行的方法只是对fmt.Scanln()的简单调用。我想知道其他人如何避免主要退出。以下是一个基本的例子。这里可以使用什么想法或最佳实践?如何保持长时间运行的Go程序,运行?
我考虑创建一个频道,并通过在所述频道上接收延迟主体退出,但是我认为如果我的所有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)
}
}
Go的运行时的当前设计假定程序员负责检测何时终止goroutine以及何时终止程序。程序员需要计算goroutines和整个程序的终止条件。程序可以通过调用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()
之前被调用,因此有必要在main()
创建至少1够程,而随后的够程可以在程序的任何部分创建:
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()
}
彻底的答案和很好的解释。我可能会与频道接收,因为我的例程被埋在其他服务包中。我不想在任何地方做计数器计数。如果我决定使用信号或其他IPC将关闭逻辑添加到程序中,该通道将工作良好...谢谢! – Nate 2012-03-03 16:54:49
您可以使用Supervisor(http://supervisord.org/)来守护进程。你的函数永远只是一个运行的过程,它将处理函数main的一部分。您可以使用管理员控制界面启动/关闭/检查您的流程。
一个有趣的系统,但它似乎是一个实际的程序,而不是我将使用的Go代码。我要编辑我的问题,因为我特意试图找出保持主体退出的最佳实践。现在,我正在使用fmt.Scanln(),我想知道是否有更好的方法......但感谢Supervisor的提示。我可以看到我们进一步调查。 – Nate 2012-03-03 06:12:50
永远阻止。例如,
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)
}
}
这里永远是使用渠道
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)
}
}
Go的runtime包有一个名为runtime.Goexit
功能,将做的正是你想要的是简单的块。在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")
}
这非常酷。但是,我不得不怀疑,为什么他们选择让程序崩溃而不是让它正常退出。是否有另一个goroutine处理应用程序关闭的想法,它会在接收到信号时调用exit?我只是想知道如何正确使用这个功能。 – Nate 2016-08-10 21:23:09
@注意是正确的。例如查看这个[code](https://play.golang.org/p/8N5Yr3ZNPT),Go例程2退出程序并且程序不会崩溃。如果Goexit崩溃了你的程序,那是因为所有其他的Go例程都已经完成。我很喜欢它如何崩溃,让我知道它没有做任何事情,不像说'select {}',即使没有发生任何事情,它会永远阻止。 – jmaloney 2016-08-11 22:40:39
这需要更多upvotes。 – 2016-09-15 05:52:38
例子,我发现6种方法,使其 - HTTP:// pliutau。com/different-ways-block-go-runtime-forever/ – pltvs 2017-04-25 01:40:05