我正在努力使用频道来实现队列。具体来说,我试图使用通道的大小来限制同时goroutine的数量。也就是说,我写了以下代码:
package main
import "fmt"
import "time"
import "math/rand"
func runTask (t string, ch *chan bool) {
start := time.Now()
fmt.Println("starting task", t)
time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time
fmt.Println("done running task", t, "in", time.Since(start))
<- *ch
}
func main() {
numWorkers := 3
files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
activeWorkers := make(chan bool, numWorkers)
for _, f := range files {
activeWorkers <- true
fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers))
go runTask(f, &activeWorkers)
}
select{}
}
现在,代码崩溃了:
throw: all goroutines are asleep - deadlock!
我的期望是对select的调用将永远阻止,让goroutines终止而不会出现死锁。
所以我有一个双重问题:为什么不选择永久阻塞,而不是在for循环中抛出一个time.Sleep()调用,我怎样才能避免死锁?
干杯,
-mtw
答案 0 :(得分:6)
Arlen Cuss已经写了一个很好的答案。我只是想为你的工作队列建议另一种设计。您可以只生成有限数量的工作人员goroutine,而不是限制您的频道可以缓冲的条目数量。这样的事情:
package main
import "fmt"
import "time"
import "math/rand"
func runTask(t string) string {
start := time.Now()
fmt.Println("starting task", t)
time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time
fmt.Println("done running task", t, "in", time.Since(start))
return t
}
func worker(in chan string, out chan string) {
for t := range in {
out <- runTask(t)
}
}
func main() {
numWorkers := 3
// spawn workers
in, out := make(chan string), make(chan string)
for i := 0; i < numWorkers; i++ {
go worker(in, out)
}
files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
// schedule tasks
go func() {
for _, f := range files {
in <- f
}
}()
// get results
for _ = range files {
<-out
}
}
如果您只想等到所有任务都执行完毕,也可以使用sync.WaitGroup,但使用out
频道的优势在于您可以稍后汇总结果。例如,如果每个任务返回该文件中的单词数,则可以使用最终循环来总结所有单个单词计数。
答案 1 :(得分:4)
首先,您不需要将指针传递给通道;通道,如地图和其他通道
are references,意味着不复制基础数据,只指向实际数据的指针。如果您需要指向chan
本身的指针,您就会知道该时间到了。
发生崩溃是因为程序进入了阻止每个goroutine的状态。这个应该是不可能的;如果每个goroutine被阻止,那么就没有可能的进程可以唤醒另一个goroutine(并且你的程序将被挂起)。
主要的goroutine以select {}
结束 - 不等待任何人,只是悬挂。一旦最后一个runTask
goroutine完成,只剩下主要的goroutine,它正在等待没有人。
你需要添加一些方法来了解每个goroutine何时完成;也许另一个频道可以接收完成事件。
这有点难看,但可能会有所启发。
package main
import "fmt"
import "time"
import "math/rand"
func runTask(t string, ch chan bool, finishedCh chan bool) {
start := time.Now()
fmt.Println("starting task", t)
time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time
fmt.Println("done running task", t, "in", time.Since(start))
<-ch
finishedCh <- true
}
func main() {
numWorkers := 3
files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
activeWorkers := make(chan bool, numWorkers)
finishedWorkers := make(chan bool)
done := make(chan bool)
go func() {
remaining := len(files)
for remaining > 0 {
<-finishedWorkers
remaining -= 1
}
done <- true
}()
for _, f := range files {
activeWorkers <- true
fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers))
go runTask(f, activeWorkers, finishedWorkers)
}
<-done
}
答案 2 :(得分:2)
tux21b已经发布了一个更惯用的解决方案,但我想以不同的方式回答你的问题。 select {}会永远阻止,是的。当所有goroutine被阻止时发生死锁。如果你所有的其他goroutine都完成了,那么你只剩下被阻挡的主要goroutine,这是一个僵局。
通常情况下,您希望在所有其他人完成之后在您的主要goroutine中执行某些操作,无论是使用他们的结果,还是只是清理,为此您可以执行tux21b建议的操作。如果你真的只想让main完成并让剩下的goroutine完成他们的工作,那就把defer runtime.Goexit()
放在你的main函数的顶部。这将导致它退出而不退出程序。