GOOGLE ADS

среда, 13 апреля 2022 г.

Можно ли отменить незавершенные горутины?

Рассмотрим группу проверок, каждая из которых имеет независимую логику, поэтому кажется, что их можно запускать одновременно, например:

type Work struct {
//...
}
// This Check could be quite time-consuming
func (w *Work) Check() bool {
// return succeed or not
//...
}
func CheckAll(works []*Work) {
num:= len(works)
results:= make(chan bool, num)
for _, w:= range works {
go func(w *Work) {
results <- w.Check()
}(w)
}
for i:= 0; i < num; i++ {
if r:= <-results;!r {
ReportFailed()
break;
}
}
}
func ReportFailed() {
//...
}

Что касается results, если логика не зависит от того, какая работа не работает, мы утверждаем, что все работы полностью терпят неудачу, остальные значения в канале бесполезны. Пусть оставшиеся незавершенные горутины продолжают выполняться и отправлять результаты в канал бессмысленно и расточительно, особенно когда w.Check()это занимает довольно много времени. Идеальный эффект похож на:

 for _, w:= range works {
if!w.Check() {
ReportFailed()
break;
}
}

Это только запускает необходимые проверки, а затем прерывает работу, но в последовательном непараллельном сценарии.

Итак, можно ли отменить эти незавершенные горутины или отправку на канал?


Решение проблемы


Отмена (блокировки) отправки

Ваш первоначальный вопрос касался того, как отменить операцию отправки. Отправка на канал в основном «мгновенная». Отправка по каналу блокируется, если буфер канала заполнен и нет готового получателя.

Вы можете «отменить» эту отправку, используя selectоператор и cancelканал, который вы закрываете, например:

cancel:= make(chan struct{})
select {
case ch <- value:
case <- cancel:
}

Закрытие cancelканала с помощью close(cancel)другой горутины заставит указанный выше выбор отказаться от отправки ch(если он блокируется).

Но, как сказано, отправка является «мгновенной» на «готовом» канале, и отправка сначала оценивает отправляемое значение:

results <- w.Check()

Это сначала должно быть запущено w.Check(), и как только это будет сделано, его возвращаемое значение будет отправлено на results.

Отмена вызова функции

Итак, что вам действительно нужно, так это отменить w.Check()вызов метода. Для этого идиоматическим способом является передача context.Contextзначения, которое вы можете отменить, и w.Check()оно само должно отслеживать и «подчиняться» этому запросу отмены.

См. Завершение выполнения функции при отмене контекста.

Обратите внимание, что ваша функция должна явно поддерживать это. Нет неявного завершения вызовов функций или горутин, см. Отмена блокирующей операции в Go.

Итак, вы Check()должны выглядеть примерно так:

// This Check could be quite time-consuming
func (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {
// Do your thing and monitor the context!
select {
case <-ctx.Done():
return false
case <-time.After(workDuration): // Simulate work
return true
case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec
return false
}
}

А CheckAll()может выглядеть так:

func CheckAll(works []*Work) {
ctx, cancel:= context.WithCancel(context.Background())
defer cancel()
num:= len(works)
results:= make(chan bool, num)
wg:= &sync.WaitGroup{}
for i, w:= range works {
workDuration:= time.Second * time.Duration(i)
wg.Add(1)
go func(w *Work) {
defer wg.Done()
result:= w.Check(ctx, workDuration)
// You may check and return if context is cancelled
// so result is surely not sent, I omitted it here.
select {
case results <- result:
case <-ctx.Done():
return
}
}(w)
}
go func() {
wg.Wait()
close(results) // This allows the for range over results to terminate
}()
for result:= range results {
fmt.Println("Result:", result)
if!result {
cancel()
break
}
}
}

Тестирование:

CheckAll(make([]*Work, 10))

Вывод (попробуйте на Go Playground ):

Result: true
Result: true
Result: true
Result: false

Мы trueпечатаем 3 раза (работы, которые завершаются менее чем за 2,5 секунды), затем запускается симуляция сбоя, возвращается falseи завершает все остальные задания.

Обратите внимание, что sync.WaitGroupв приведенном выше примере это не является строго необходимым, так как resultsимеет буфер, способный хранить все результаты, но в целом это все еще хорошая практика (если вы будете использовать меньший буфер в будущем).

См. также: Закройте несколько горутин, если в одном из них возникла ошибка.

Комментариев нет:

Отправить комментарий

Laravel Datatable addColumn returns ID of one record only

Я пытаюсь использовать Yajra Datatable для интеграции DataTable на свой веб-сайт. Я смог отобразить таблицу, но столкнулся с проблемой. В по...