如何保持长时间运行的Go程序,运行?

时间:2012-03-03 05:45:49

标签: go daemon goroutine

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

6 个答案:

答案 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退出,程序崩溃。

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

答案 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的一部分。您可以使用管理程序控制界面来启动/关闭/检查您的流程。