일부 goroutines에 일부 부하를 분산하고 싶습니다. 사전에 작업의 수를 알고 있다면 쉽게 구성 할 수 있습니다. 예를 들어 대기 그룹과 함께 팬을 만날 수 있습니다.golang의 재귀 동시성
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
그러나 제 경우에는 각 작업이 더 많은 작업을 반환합니다. 예를 들어 크롤링 된 웹에서 모든 링크를받는 크롤러라고 말하십시오. 필자의 첫 번째 직감은 수행 된 작업 수와 보류중인 작업 수를 추적하는 메인 루프를 만드는 것이 었습니다. 완료되면 나는 모든 신호에 완료 신호를 보낸다 :
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가 goroutines의 수보다 크면 첫 번째 작업이 완료되면 주 루프가 멈 춥니 다. 그러나 mainLoop이 쓰기를 기다리고 있기 때문에 피드 백이 차단되어 첫 번째 작업을 완료 할 수 없습니다.
비공식적으로 채널에 게시하는 것이 쉬운 방법 중 하나입니다. feedBackChannel <- task
을 수행하는 대신 go func() {feedBackChannel <- task}()
을 수행하십시오. 자, 이것은 끔찍한 해킹처럼 느껴집니다. 특별히 수십만 개의 작업이있을 수 있기 때문에 특별히 그렇습니다.
이 교착 상태를 피하는 좋은 방법은 무엇입니까? 나는 동시성 패턴을 찾았지만, 대부분은 나중에 패닝 (fanning out)이나 파이프 라인 (pipeline)과 같은 더 단순한 것들이다.
설명이 너무 복잡하기는하지만 완전히 이해할 수있는 메모가 2 개 있습니다. 1. goroutine 내부에서 waitGroup.Add()를 잘못 수행 한 경우,이를 호출하기 전에 완료해야합니다. goroutine이 시작되면 즉시 waitGroup.Done()을 호출합니다. 2. 왜 피드백 채널이 필요한지 명확하지 않습니다. 나에게 필요에 따라 새로운 grorutines을 생성하고 메인 스레드에서 watGroup.Wait()을 수행하면된다. 하지만 몇 가지 요구 사항이 누락되었을 수 있습니다. –
@AlexanderTrakhimenok 새로운 goroutines을 만들면 효과가 있지만 goroutines를 재사용하고 그 양을 제한하면 리소스를 덜 소비하게 될 것입니다. 맞습니까? 제 경우에는 수십만 건의 작업을 기대하고 있습니다. (BTW, 나는 기다림 그룹을 고정시켰다.) –
Go 루틴은 매우 가볍다. 문제없이 수백만 개의 골 루틴을 생성하는 것이 일반적이다. 속도 제한 패턴을 살펴볼 수도 있지만 https://gobyexample.com/rate-limiting –