我想在一些goroutines上分配一些负载。如果预先知道任务数量,那么很容易组织。例如,我可以和一个等待小组一起玩。
nTasks := 100
nGoroutines := 10
// it is important that this channel is not buffered
ch := make(chan *Task)
done := make(chan bool)
var w sync.WaitGroup
// Feed the channel until done
go func () {
for i:= 0; i < nTasks; i++ {
task := getTaskI(i)
ch <- task
}
// as ch is not buffered once everything is read we know we have delivered all of them
for i:=0; i < nGoroutines; i++ {
done <- false
}
}()
for i:= 0; i < nGoroutines; i ++ {
w.Add(1)
go func () {
defer w.Done()
select {
case task := <-ch:
doSomethingWithTask(task)
case <- done:
return
}
}()
}
w.Wait()
// All tasks done, all goroutines closed
但是,在我的情况下,每个任务都会返回更多要完成的任务。比如说我们从抓取的网络接收所有链接的抓取工具。我最初的预感是有一个主循环,我跟踪完成的任务数和待处理的任务。当我完成后,我向所有goroutines发送一个结束信号:
nGoroutines := 10
ch := make(chan *Task, nGoroutines)
feedBackChannel := make(chan * Task, nGoroutines)
done := make(chan bool)
for i:= 0; i < nGoroutines; i ++ {
go func () {
select {
case task := <-ch:
task.NextTasks = doSomethingWithTask(task)
feedBackChannel <- task
case <- done:
return
}
}()
}
// seed first task
ch <- firstTask
nTasksRemaining := 1
for nTasksRemaining > 0 {
task := <- feedBackChannel
nTasksRemaining -= 1
for _, t := range(task.NextTasks) {
ch <- t
nTasksRemaining++
}
}
for i:=0; i < nGoroutines; i++ {
done <- false
}
但是,这会产生死锁。例如,如果NextTasks大于goroutine的数量,那么当第一个任务完成时,主循环将停止。但是第一个任务无法完成,因为自从mainLoop等待写入后,feedBack被阻止。
One&#34; easy&#34;解决这个问题的方法是异步发布到频道:
而不是feedBackChannel <- task
做go func () {feedBackChannel <- task}()
。现在,这感觉就像一个可怕的黑客。特别是因为可能有数十万个任务。
什么是避免这种僵局的好方法?我已经搜索了并发模式,但主要是更简单的事情,如扇出或管道,后期不会影响前面的步骤。
答案 0 :(得分:0)
如果我正确理解您的问题,您的解决方案非常复杂。这里有一些观点。希望它有所帮助。
ch
频道代替select
而不是range
通过频道获取任务{/ 1}}。ch
和feedBackChannel
分开只是将您拥有的每项任务都分成ch
并增加其容量。cap(ch) - len(ch) < threshold
,您也可以记录警告)。如果您创建一个容量为100万的通道(指针),则需要大约8 * 1e6 ~= 8MB
个ram。