2012-03-03 133 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