英文:
Golang: Why os.Exit doesn't work inside goroutines
问题
我有一个非常简单的算法研究项目。当成功到来时,goroutine 应该通过 os.Exit(0) 关闭(结束)。我等了一天,两天...怎么回事?:)
以下是简单的代码:
package main
import "os"
func main() {
for {
go func() { os.Exit(0) }()
}
}
我的问题是:
- 为什么 os.Exit() 不会终止 goroutine?
- 终止(停止) goroutine 执行的正确方法是什么?
Playground: http://play.golang.org/p/GAeOI-1Ksc
英文:
I have a research program with very simple algorithm. When success is coming goroutine should be close (end) via os.Exit(0). I'm wait one day, two day.... What?
Here is the simple code
package main
import "os"
func main() {
for {
go func() { os.Exit(0) }()
}
}
And my questions:
- Why os.Exit doesn't terminate the goroutine?
- What is correct way to terminate (stop) goroutine execute?
Playground: http://play.golang.org/p/GAeOI-1Ksc
答案1
得分: 18
你遇到了Go调度器的一个棘手问题。答案是,os.Exit
确实会导致整个进程退出,但是你的代码中,goroutine从未运行。
可能发生的情况是,for循环不断向可用的goroutine列表中添加新的goroutine,但由于整个进程只在一个操作系统线程中运行,Go调度器从未安排其他的goroutine运行,只是一直运行那个for循环,而没有运行你创建的任何goroutine。请尝试以下代码:
package main
import "os"
func main() {
for {
go func() { os.Exit(0) }()
func() {}()
}
}
如果你在Go Playground上运行它,应该可以正常工作(这里是链接)。
好的,上面的代码能够正常工作,而你的代码不能,这应该是相当神秘的。之所以能够正常工作的原因是,Go调度器实际上是非抢占式的。这意味着,除非某个goroutine自愿地决定给调度器运行其他东西的机会,否则不会运行其他东西。
显然,你从未编写过包含命令以便给调度器运行的代码。发生的情况是,在编译你的代码时,Go编译器会自动将这些命令插入到你的代码中。而上面代码能够正常工作的关键在于:goroutine可能决定运行调度器的时机之一是在调用函数时。因此,通过添加func() {}()
调用(显然什么也不做),我们允许编译器插入对调度器的调用,使得这段代码有机会调度不同的goroutine。因此,一个被创建的goroutine运行,调用os.Exit
,进程退出。
编辑:在编译器内联调用(或者在这种情况下,完全删除它,因为它什么也不做)的情况下,函数调用本身可能不足够。另一方面,runtime.Gosched()
保证可以工作。
英文:
You've run into a sticky corner of the Go scheduler. The answer is that os.Exit
does cause the entire process to exit, but the way you had it, the goroutines were never running.
What probably happened was that the for loop kept adding new goroutines to the list of available goroutines, but since the entire process was only running in one OS thread, the Go scheduler never got around to actually scheduling a different goroutine, and just kept running that for loop without ever running any of the goroutines you'd spawned. Try this instead:
package main
import "os"
func main() {
for {
go func() { os.Exit(0) }()
func() {}()
}
}
If you run it on the Go Playground, it should work (in fact, here's a link).
OK, the fact that the above code works while yours doesn't should be pretty mysterious. The reason this works is that the Go scheduler is actually non-preempting. What that means is that unless a given goroutine voluntarily decides to give the scheduler the option to run something else, nothing else will run.
Now obviously you've never written code that includes commands to give the scheduler a chance to run. What happens is that when your code is compiled, the Go compiler automatically inserts these into your code. And here's the key to why the above code works: one of the times that a goroutine might decide to run the scheduler is when a function is called. So by adding the func() {}()
call (which obviously does nothing), we've allowed the compiler to add in a call to the scheduler, giving this code the opportunity to schedule different goroutines. Thus, one of the spawned goroutines runs, calls os.Exit
, and the process exits.
EDIT: The function call itself may not be sufficient in the event that the compiler inlines the call (or, in this case, removes it entirely since it does nothing). runtime.Gosched()
, on the other hand, is guaranteed to work.
答案2
得分: 0
你可以通过从函数中返回来终止一个 goroutine。如果你需要确保 goroutine 执行完毕,你需要等待 goroutine 完成。通常可以使用 sync.WaitGroup
或通过通道来同步 goroutine。
在你的示例中,首先需要确保不会产生无限数量的 goroutine。因为 main
和新的 goroutine 之间没有同步点,所以不能保证它们中的任何一个在主循环运行时执行 os.Exit
调用。
等待任意数量的 goroutine 完成的常见方法是使用 sync.WaitGroup
,它会确保它们在 main
退出之前都已执行完毕。
wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go func() { defer wg.Done() }()
}
wg.Wait()
fmt.Println("done")
英文:
You terminate a goroutine by returning from the function. If you need to ensure that the goruotine runs to completion, you need to wait for the goroutine to complete. This is usually done with a sync.WaitGroup
, or by synchronizing the goroutines via channels.
In your example, you first need to ensure that there is no possibility of spawning an infinite number of goroutines. Because there's no synchronization points between main
and the new goroutines, there's no guarantee that any of them will execute the os.Exit
call while the main loop is running.
The usual way of waiting for any number of goroutines to complete is to use a sync.WaitGroup
, which will ensure they have all executed before main
exits.
wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go func() { defer wg.Done() }()
}
wg.Wait()
fmt.Println("done")
答案3
得分: -4
实现一个deadhand或者kill switch
package main
import (
"fmt"
"time"
"os"
)
const maxNoTickle = 50 // 在没有tickles的情况下,达到这个数量后退出
const maxWorking = 20 // pretendWork()将进行这么多次tickles
const deadTicks = 250 // deadHand()检查tickles的毫秒数
const reportTickles = 4 // 连续的tickles或者没有tickles时打印一些内容
var (
tickleMe bool // 告诉deadHand()我们还活着
countNoTickle int // 连续没有tickles的次数
countGotTickle int // 连续的tickles的次数
)
/**
* deadHand() - 回调函数,如果一段时间后没有人检查,则终止程序
*/
func deadHand() {
if !tickleMe {
countNoTickle++
countGotTickle = 0
if countNoTickle > maxNoTickle {
fmt.Println("达到最长无tickles时间。退出程序!")
// panic("No real panic. Just checking stack")
os.Exit(0)
}
if countNoTickle % reportTickles == 0 {
// 连续没有tickles时打印点
fmt.Printf(".")
}
} else {
countNoTickle = 0
countGotTickle++
tickleMe = false // FIXME: 这里可能存在竞争条件
if countGotTickle % reportTickles == 0 {
// 连续的tickles时打印波浪线
fmt.Printf("~")
}
}
// 再次调用自己
time.AfterFunc(deadTicks * time.Millisecond, deadHand)
}
/**
* init() - 启动deadHand所需的函数
*/
func init() {
time.AfterFunc(250 * time.Millisecond, deadHand)
tickleMe = true
}
/**
* pretendWork() - 做你的事情
*/
func pretendWork() {
for count := 0; count < maxWorking; count++ {
tickleMe = true // FIXME: 这里可能存在竞争条件
// 打印W来假装忙碌
fmt.Printf("W")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go pretendWork()
for {
// 程序进入无限循环
}
}
以上是代码的翻译。
英文:
Implement a deadhand or kill switch
package main
import (
"fmt"
"time"
"os"
)
const maxNoTickle = 50 // will bail out after this many no tickles
const maxWorking = 20 // pretendWork() will tickle this many times
const deadTicks = 250 // milliseconds for deadHand() to check for tickles
const reportTickles = 4 // consecutive tickles or no tickles to print something
var (
tickleMe bool // tell deadHand() we're still alive
countNoTickle int // consecutive no tickles
countGotTickle int // consecutive tickles
)
/**
* deadHand() - callback to kill program if nobody checks in after some period
*/
func deadHand() {
if !tickleMe {
countNoTickle++
countGotTickle = 0
if countNoTickle > maxNoTickle {
fmt.Println("No tickle max time reached. Bailing out!")
// panic("No real panic. Just checking stack")
os.Exit(0)
}
if countNoTickle % reportTickles == 0 {
// print dot for consecutive no tickles
fmt.Printf(".")
}
} else {
countNoTickle = 0
countGotTickle++
tickleMe = false // FIXME: might have race condition here
if countGotTickle % reportTickles == 0 {
// print tilda for consecutive tickles
fmt.Printf("~")
}
}
// call ourselves again
time.AfterFunc(deadTicks * time.Millisecond, deadHand)
}
/**
* init() - required to start deadHand
*/
func init() {
time.AfterFunc(250 * time.Millisecond, deadHand)
tickleMe = true
}
/**
* pretendWork() - your stuff that does its thing
*/
func pretendWork() {
for count := 0; count < maxWorking; count++ {
tickleMe = true // FIXME: might have race condition here
// print W pretending to be busy
fmt.Printf("W")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go workTillDone()
for {
// oops, program went loop-d-loopy
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论