61

我有一个用 Go 编写的长时间运行的服务器。Main 触发了几个执行程序逻辑的 goroutine。在那之后, main 没有任何用处。一旦 main 退出,程序将退出。我现在用来保持程序运行的方法只是对 fmt.Scanln() 的简单调用。我想知道其他人如何阻止 main 退出。下面是一个基本的例子。这里可以使用哪些想法或最佳实践?

我考虑过创建一个通道并通过在所述通道上接收来延迟 main 的退出,但我认为如果我所有的 goroutine 在某个时候都变得不活动,这可能会出现问题。

旁注:在我的服务器(不是示例)中,程序实际上并没有连接到外壳运行,因此无论如何与控制台交互都没有意义。现在它有效,但我正在寻找“正确”的方式,假设有一个。

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

6 回答 6

65

永远封锁。例如,

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)
    }
}
于 2012-03-03T09:48:20.607 回答
33

Go 运行时的当前设计假设程序员负责检测何时终止 goroutine 以及何时终止程序。程序员需要计算 goroutine 和整个程序的终止条件。程序可以通过调用os.Exit或从main()函数返回以正常方式终止。

通过在该通道上立即接收来创建通道并延迟退出main()是防止main退出的有效方法。但它并没有解决检测何时终止程序的问题。

如果在函数进入 wait-for-all-goroutines-to-terminate 循环之前无法计算 goroutines 的数量,则main()需要发送 deltas 以便main函数可以跟踪有多少 goroutines 正在运行:

// 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(),因此需要在其中创建至少 1 个 goroutine,main()而后续的 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()
}
于 2012-03-03T08:13:39.560 回答
26

Go 的运行时包有一个名为的函数runtime.Goexit,它将完全按照您的意愿行事。

从主 goroutine 调用 Goexit 会终止该 goroutine 而不会返回 func main。由于 func main 没有返回,程序继续执行其他 goroutine。如果所有其他 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")
}
于 2016-08-09T16:59:15.203 回答
19

没有人提到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)
    }
}
于 2020-12-03T13:42:44.987 回答
12

这是一个永远使用通道的简单块

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)
    }
}
于 2013-08-02T13:09:37.307 回答
0

您可以使用 Supervisor ( http://supervisord.org/ ) 守护进程。你的函数永远只是一个它运行的进程,它会处理你的 main 函数的一部分。您将使用主管控制界面来启动/关闭/检查您的流程。

于 2012-03-03T05:54:47.057 回答