golang

时间:2017-12-07 10:50:08

标签: go concurrency

我想在一些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 <- taskgo func () {feedBackChannel <- task}()。现在,这感觉就像一个可怕的黑客。特别是因为可能有数十万个任务。

什么是避免这种僵局的好方法?我已经搜索了并发模式,但主要是更简单的事情,如扇出或管道,后期不会影响前面的步骤。

1 个答案:

答案 0 :(得分:0)

如果我正确理解您的问题,您的解决方案非常复杂。这里有一些观点。希望它有所帮助。

  • 正如人们在评论中提到的,启动goroutine很便宜(内存和它们之间的切换比操作系统级别便宜得多)并且你可能有十万个。让我们假设你想要有工人goroutines。
  • 您可以关闭ch频道代替select而不是range通过频道获取任务{/ 1}}。
  • 我认为将chfeedBackChannel分开只是将您拥有的每项任务都分成ch并增加其容量。
  • 如上所述,当您尝试将新任务排入队列时,可能会陷入僵局。我的解决方案非常天真。只需增加容量,直到您确定它不会溢出(如果cap(ch) - len(ch) < threshold,您也可以记录警告)。如果您创建一个容量为100万的通道(指针),则需要大约8 * 1e6 ~= 8MB个ram。