2012-03-03 77 views
29

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

Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.

例子,我发现6种方法,使其 - HTTP:// pliutau。com/different-ways-block-go-runtime-forever/ – pltvs 2017-04-25 01:40:05

回答

18

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() 
} 
+0

彻底的答案和很好的解释。我可能会与频道接收,因为我的例程被埋在其他服务包中。我不想在任何地方做计数器计数。如果我决定使用信号或其他IPC将关闭逻辑添加到程序中,该通道将工作良好...谢谢! – Nate 2012-03-03 16:54:49

1

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

+0

一个有趣的系统,但它似乎是一个实际的程序,而不是我将使用的Go代码。我要编辑我的问题,因为我特意试图找出保持主体退出的最佳实践。现在,我正在使用fmt.Scanln(),我想知道是否有更好的方法......但感谢Supervisor的提示。我可以看到我们进一步调查。 – Nate 2012-03-03 06:12:50

36

永远阻止。例如,

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

简单。完成工作。给予好评。 – Nate 2012-03-03 16:56:20

+0

这种方法似乎不再适用于最新版本,抛出错误'goroutine 1 [select(no cases)]: main.main()' – 2015-06-20 17:07:59

+1

@dark_ruby:它适用于最新版本的Go:'go version devel + 13c44d2 Sat Jun 20 10:35:38 2015 +0000 linux/amd64'。我们如何准确地重现你的失败?例如,非常精确,“最新去”太含糊。 – peterSO 2015-06-20 17:41:12

1

这里永远是使用渠道

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

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

这非常酷。但是,我不得不怀疑,为什么他们选择让程序崩溃而不是让它正常退出。是否有另一个goroutine处理应用程序关闭的想法,它会在接收到信号时调用exit?我只是想知道如何正确使用这个功能。 – Nate 2016-08-10 21:23:09

+1

@注意是正确的。例如查看这个[code](https://play.golang.org/p/8N5Yr3ZNPT),Go例程2退出程序并且程序不会崩溃。如果Goexit崩溃了你的程序,那是因为所有其他的Go例程都已经完成。我很喜欢它如何崩溃,让我知道它没有做任何事情,不像说'select {}',即使没有发生任何事情,它会永远阻止。 – jmaloney 2016-08-11 22:40:39

+0

这需要更多upvotes。 – 2016-09-15 05:52:38