Я бы использовал один канал для передачи результатов, поэтому гораздо проще собирать результаты, и он "масштабируется" автоматически по своей природе.Если вам нужно определить источник результата, просто используйте оболочку, которая включает источник.Примерно так:
type Result struct {
ID string
Result bool
}
Чтобы имитировать «настоящую» работу, работники должны использовать цикл, выполняющий свою работу итеративным образом, и в каждой итерации они должны проверять сигнал отмены.Примерно так:
func foo(ctx context.Context, pretendWorkMs int, resch chan<- Result) {
log.Printf("foo started...")
for i := 0; i < pretendWorkMs; i++ {
time.Sleep(time.Millisecond)
select {
case <-ctx.Done():
log.Printf("foo terminated.")
return
default:
}
}
log.Printf("foo finished")
resch <- Result{ID: "foo", Result: false}
}
В нашем примере bar()
- это то же самое, просто замените все слова foo
на bar
.
А теперь выполняйте задания и завершайте остальныерано, если кто-то оправдывает наши ожидания, выглядит так:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resch := make(chan Result, 2)
log.Println("Kicking off workers...")
go foo(ctx, 3000, resch)
go bar(ctx, 5000, resch)
for i := 0; i < cap(resch); i++ {
result := <-resch
log.Printf("Result of %s: %v", result.ID, result.Result)
if !result.Result {
cancel()
break
}
}
log.Println("Done.")
Запуск этого приложения приведет к выводу (попробуйте на Go Playground ):
2009/11/10 23:00:00 Kicking off workers...
2009/11/10 23:00:00 bar started...
2009/11/10 23:00:00 foo started...
2009/11/10 23:00:03 foo finished
2009/11/10 23:00:03 Result of foo: false
2009/11/10 23:00:03 Done.
Некоторыевещи на заметку.Если мы завершим досрочно из-за неожиданного результата, будет вызвана функция cancel()
, и мы вырвемся из цикла.Может быть, остальные работники также завершают свою работу одновременно и отправляют свой результат, что не будет проблемой, поскольку мы использовали буферизованный канал, поэтому их отправка не будет блокироваться, и они будут правильно завершены.Кроме того, если они не завершаются одновременно, они проверяют ctx.Done()
в своем цикле, и они завершаются рано, поэтому процедуры хорошо очищаются.
Также обратите внимание, что вывод приведенного выше кода не печатаетbar terminated
.Это связано с тем, что функция main()
завершается сразу после цикла, а после завершения функции main()
она не ожидает завершения других не-1027 * подпрограмм.Подробнее см. Нет вывода из goroutine в Go .Если приложение не завершится немедленно, мы увидим и эту строку.Если мы добавим time.Sleep()
в конце main()
:
log.Println("Done.")
time.Sleep(3 * time.Millisecond)
Вывод будет (попробуйте на Go Playground ):
2009/11/10 23:00:00 Kicking off workers...
2009/11/10 23:00:00 bar started...
2009/11/10 23:00:00 foo started...
2009/11/10 23:00:03 foo finished
2009/11/10 23:00:03 Result of foo: false
2009/11/10 23:00:03 Done.
2009/11/10 23:00:03 bar terminated.
Теперь, если вам нужно подождать, пока все рабочие закончатся «нормально» или «рано», прежде чем двигаться дальше, вы можете достичь этого разными способами.
Одним из способов является использование sync.WaitGroup
.Для примера см. Предотвращение завершения функции main () до завершения выполнения программ в Golang .Другим способом было бы, чтобы каждый работник отправлял Result
независимо от того, как они заканчиваются, и Result
может содержать условие завершения, например, normal
или aborted
.И процедура main()
может продолжать цикл приема до тех пор, пока не получит значения n
из resch
.Если выбрано это решение, вы должны убедиться, что каждый работник отправляет значение (даже если возникает паника), чтобы не блокировать main()
в таких случаях (например, с использованием defer
).