简体   繁体   English

如何最好地保持长时间运行的 Go 程序运行?

[英]How best do I keep a long running Go program, running?

I've a long running server written in Go.我有一个用 Go 编写的长期运行的服务器。 Main fires off several goroutines where the logic of the program executes. Main 会触发几个执行程序逻辑的 goroutine。 After that main does nothing useful.在那之后 main 没有任何用处。 Once main exits, the program will quit.一旦 main 退出,程序将退出。 The method I am using right now to keep the program running is just a simple call to fmt.Scanln().我现在使用的保持程序运行的方法只是对 fmt.Scanln() 的简单调用。 I'd like to know how others keep main from exiting.我想知道其他人如何防止 main 退出。 Below is a basic example.下面是一个基本的例子。 What ideas or best practices could be used here?这里可以使用哪些想法或最佳实践?

I considered creating a channel and delaying exit of main by receiving on said channel, but I think that could be problematic if all my goroutines become inactive at some point.我考虑过创建一个通道并通过在所述通道上接收来延迟 main 的退出,但我认为如果我的所有 goroutine 在某个时候变得不活动,这可能会出现问题。

Side note: In my server (not the example), the program isn't actually running connected to a shell, so it doesn't really make sense to interact with the console anyway.旁注:在我的服务器(不是示例)中,该程序实际上并未连接到 shell 运行,因此无论如何与控制台交互都没有意义。 For now it works, but I'm looking for the "correct" way, assuming there is one.现在它有效,但我正在寻找“正确”的方式,假设有一个。

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

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. Go 运行时的当前设计假设程序员负责检测何时终止 goroutine 以及何时终止程序。 The programmer needs to compute the termination condition for goroutines and also for the entire program.程序员需要计算 goroutine 以及整个程序的终止条件。 A program can be terminated in a normal way by calling os.Exit or by returning from the main() function.可以通过调用os.Exit或从main()函数返回以正常方式终止程序。

Creating a channel and delaying exit of main() by immediately receiving on said channel is a valid approach of preventing main from exiting.创建一个通道并通过立即在该通道上接收来延迟main()的退出是防止main退出的有效方法。 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:如果在main()函数进入 wait-for-all-goroutines-to-terminate 循环之前无法计算 goroutines 的数量,您需要发送增量以便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
}

An alternative approach is to replace the channel with sync.WaitGroup .另一种方法是用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:这种方法的一个缺点是需要在调用wg.Wait()之前调用wg.Add(int) ,因此需要在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()
}

Go's runtime package has a function called runtime.Goexit that will do exactly what you want. Go 的运行时包有一个名为runtime.Goexit的函数,它可以完全满足您的需求。

Calling Goexit from the main goroutine terminates that goroutine without func main returning.从主协程调用 Goexit 会终止该协程,而不会返回 func main。 Since func main has not returned, the program continues execution of other goroutines.由于 func main 没有返回,程序继续执行其他 goroutine。 If all other goroutines exit, the program crashes.如果所有其他 goroutine 退出,程序就会崩溃。

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

Nobody mentioned signal.Notify(c chan<- os.Signal, sig ...os.Signal)没有人提到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)
    }
}

Here is a simple block forever using channels这是一个永远使用通道的简单块

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

You could daemonize the process using Supervisor ( http://supervisord.org/ ).您可以使用 Supervisor ( http://supervisord.org/ ) 守护进程。 Your function forever would just be a process that it runs, and it would handle the part of your function main.你的函数永远只是一个它运行的进程,它将处理你的函数 main 的一部分。 You would use the supervisor control interface to start/shutdown/check on your process.您将使用主管控制界面来启动/关闭/检查您的过程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM