【发布时间】:2021-03-07 21:22:12
【问题描述】:
试图让 2 个 Goroutine 在我的项目中正常运行。尽管这一切都在起作用,但我 100% 确信它做得很差......甚至可能是错误的......ly(这甚至是一个词吗?)。
无论如何,这个项目的基本概念是在预先选择的时间内运行一些重复的工作,同时允许在时间结束之前中止运行。
这是我到目前为止的混乱(我只包括了重要的部分,以确保每个人的理智......并隐藏我可怕的编码):
两个 Goroutine:
-
BenchTimer()是一个简单的运行时倒计时计时器,它会在设定的时间内执行一些重复工作。 -
AbortTest()是一个键盘侦听器,用于从键盘捕获“ESC”(或我想要的任何其他)按键以充当“用户中止”。
每个 Goroutine 在成功运行后(即BenchTimer() 完成倒计时或 AbortTest() 捕获中止按键),通过公共通道testAction 发送消息。我使用一个频道,因为这是 OR 有点像(即您不能同时完成倒计时和中止。)。如果BenchTimer() 完成,那么它会沿通道发送“完成”。如果AbortTest()“完成”,它将沿通道发送“中止”。 [到目前为止,这一切似乎都在工作......]
我遇到的下一个问题是如何杀死不是“赢家”的 Goroutine...(即如果 BenchTimer() 正常完成,那么我需要以某种方式杀死 AbortTest()...反之亦然。)经过一堆搜索,我发现不可能在外部杀死一个 Goroutine,但它可以在内部完成......所以我想出了为每个 Goroutine 使用第二个通道来充当一种“终止信号”行:killAbortTest 和 killBenchTimer。
为了将这一切联系在一起,我评估了testAction 频道的结果。因为这个频道会告诉我哪个 Goroutine “赢了”,我可以使用这个知识来发送正确的(即相反的)“kill signal”,让“loser” Goroutine 自行终止。
注意:... 仅表示其他代码存在,但由于本文不需要而被删除。
func main() {
...
testAction := make(chan string) // Action Result (Timer "Complete" or User "Abort")
killAbortTest := make(chan bool) // Kill AbortTest() Goroutine when BenchTimer() completes.
killBenchTimer := make(chan bool) // Kill BenchTimer() Goroutine when AbortTest() completes.
go BenchTimer(testAction, killBenchTimer) // Run BenchTimer() as Goroutine
go AbortTest(testAction, killAbortTest) // Run AbortTest() as Goroutine
// Program should wait here until it receives something on testAction channel.
actionVal := <-testAction
// Evaluate the testAction to kill the "loser" Goroutine
switch actionVal {
case "Abort":
killBenchTimer <- true // Abort received, signal BenchTimer() Goroutine to Quit
fmt.Println()
fmt.Println("Test Aborted")
case "Complete":
killAbortTest <- true // Countdown finished, signal AbortTest() Goroutine to Quit
fmt.Println()
fmt.Println("Test Completed")
}
...
}
// AbortTest - Listen for User Abort
func AbortTest(c chan<- string, k <-chan bool) {
if err := keyboard.Open(); err != nil {
panic(err)
}
defer func() {
_ = keyboard.Close()
}()
for {
select {
case <-k:
return
default:
_, key, err := keyboard.GetKey() // Poll for keypress
if err != nil {
panic(err)
}
if key == keyboard.KeyEsc { // ESC key was pressed
c <- "Abort"
return
}
}
}
}
// BenchTimer - Countdown Timer for BenchTest
func BenchTimer(c chan<- string, k <-chan bool) {
seconds := 0
switch testTime {
case "2-minute (fast)":
seconds = 120
case "5-minute (short)":
seconds = 300
case "10-minute (long)":
seconds = 600
case "20-minute (slow)":
seconds = 1200
}
ticker := time.Tick(time.Second)
for i := seconds; i >= 0; i-- {
select {
case <-k: // Kill Signal Received
return
default:
<-ticker
...
}
}
c <- "Complete"
}
就是这样。我的烂摊子。有很多喜欢的,但这个是我自己的。就像我说的,它现在有点用,但我希望让它变得更好。
我是否只是过度思考了整个过程,让它变得比需要的复杂得多?
任何帮助都会很棒。
【问题讨论】:
-
“错误地”是一个词,是的。 :-) “完成”频道是常见的 Go 习语。我无法评论任何我看不到的代码,但在 done 频道和另一个频道上选择是很常见的。
-
使用单个 done 通道并在
actionVal := <-testAction行之后关闭通道。这允许您删除代码以将值发送到 goroutine 特定的 done 通道,并防止 main 在其中一个 goroutine 未在其通道上接收时永远阻塞。 -
你真的不需要 AbortTest 来监听完成的事件。当 main 退出时它将退出。并将
defer keyboard.Close()移动到 main 中。如果需要,我不确定是否需要在退出序列时关闭键盘资源。它可能只是无害的。