Нет ничего плохого в бесконечном цикле как таковом. Я часто использую конструкцию for { ... }, когда условие выхода из цикла требует слишком много команд, чтобы их можно было легко поместить в условие for.
Основываясь на моем каталоге $GOPATH/src/github.com/
, который, очевидно, является довольно неполным набором образцов, я вижу сотни таких применений помимо моего собственного. Только github.com/docker/docker
использует 454 таких бесконечных цикла.
Менее подходящей является идея создания канала в цикле, который всегда передает только одно значение. Если ваша горутина всегда возвращает только одно значение, наличие этого возвращаемого значения является достаточным признаком того, что горутина выполнена. По возможности повторно используйте каналы и не закрывайте их, если вы хотите отправить больше данных позже.
Очевидно, что в вашем случае горутина в любом случае бессмысленна и предназначена только для образовательных целей. Но подумайте об этом, если вы так склонны:
package main
import (
"log"
)
func doStuff(datachan <-chan map[string]string, reschan chan<- int) {
for {
data, ok := <-datachan
if !ok {
log.Print("Channel closed.")
break
}
log.Printf("Data had %d length: %+v", len(data), data)
reschan<-len(data)
}
return
}
const workers = 3
func main() {
var datachan = make(chan map[string]string)
var reschan = make(chan int)
var inflight = 0
var inputs = []map[string]string {
map[string]string{ "hi": "world" },
map[string]string{ "bye": "space", "including": "moon" },
map[string]string{ "bye": "space", "including": "moon" },
map[string]string{ },
map[string]string{ },
}
// an inline funciton definition can change inflight within main()'s scope
processResults := func (res int) {
log.Printf("Main function got result %d", res)
inflight--
}
// start some workers
for i := 0; i < workers; i++{
go doStuff(datachan, reschan)
}
for _, data := range inputs {
//Select allows reading from reschan if datachan is not available for
// writing, thus freeing up a worker to read from datachan next loop
written := false
for written != true {
select {
case res := <-reschan:
processResults(res)
case datachan <- data:
inflight++
written = true
}
}
}
close(datachan)
for inflight > 0 {
processResults(<-reschan)
}
}
Вывод:
2020/10/31 13:15:08 Data had 1 length: map[hi:world]
2020/10/31 13:15:08 Main function got result 1
2020/10/31 13:15:08 Data had 0 length: map[]
2020/10/31 13:15:08 Main function got result 0
2020/10/31 13:15:08 Data had 0 length: map[]
2020/10/31 13:15:08 Channel closed.
2020/10/31 13:15:08 Main function got result 0
2020/10/31 13:15:08 Data had 2 length: map[bye:space including:moon]
2020/10/31 13:15:08 Channel closed.
2020/10/31 13:15:08 Main function got result 2
2020/10/31 13:15:08 Data had 2 length: map[bye:space including:moon]
2020/10/31 13:15:08 Channel closed.
2020/10/31 13:15:08 Main function got result 2
Здесь я добавляю немного больше структуры, чтобы проиллюстрировать некоторые более распространенные варианты использования for {
и close(chan)
.
Я использую потенциально бесконечный цикл в горутинах worker, которых 3 (преднамеренно создано больше, чем используется). Я считаю, сколько раз я пишу на канал, чтобы убедиться, что я прочитал каждый ответ. Когда основная горутина заканчивается, все остальные горутины бесцеремонно убиваются, поэтому я должен убедиться, что я позволил им завершиться. Подсчет результатов — один из простых способов сделать это.
Я также демонстрирую правильное использование close(chan)
. Хотя закрытие канала после использования, как это сделали вы, не является неправильным, обычно в этом нет необходимости, так как открытые каналы будут удалены сборщиком мусора после того, как все ссылки на них исчезнут. (https://stackoverflow.com/questions/8593645/is-it-ok-to-leave-a-channel-open#:%7E:text=It%27s%20OK%20to%20leave%20a,закоторымследует%20нет%20более%20данные%20.)
close(chan)
обычно используется, чтобы сообщить читателям канала, что на канале больше нет данных.
data, ok := <-datachan
Второе значение, логическое, говорит нам, прочитали ли мы data
или канал был фактически закрыт и опорожнен. Так что это часть приемника, чтобы убедиться, что мы обработали весь канал.
Поскольку я использую select
, этот код может обрабатывать inputs
произвольной длины со статическим набором рабочих процессов. Ни один из этих каналов не буферизован — читатель должен читать, чтобы писатель мог писать. Поэтому мне нужно убедиться, что я получил какие-либо результаты от работника, прежде чем я попытаюсь отправить другой ввод данных этому считывателю. Использование select
делает это тривиальным: операция завершается успешно на том канале, который готов первым (если оба канала готовы, выбор выбирается случайным образом — в этом случае он работает идеально).
В заключение, for {
, close(chan)
и select
очень хорошо работают вместе при отправке неизвестного количества входных данных в логические рабочие процессы горутины.
Несколько заключительных замечаний. В реальном мире обычно используется https://gobyexample.com/waitgroups вместо того, чтобы реализовывать все это вручную. . Концепция, как правило, та же, но в ней намного меньше отслеживания вещей, и в результате получается более чистый код. Я реализовал это сам, поэтому концепции были ясны.
И, наконец, вы заметите, что ничто не гарантирует, что рабочие горутины увидят закрытый канал до завершения программы. На практике технически возможно, что сообщение о закрытом канале может быть зарегистрировано не всеми горутинами. Но использование счетчика inflight
гарантирует, что я получу их результаты, даже если у них не было возможности наблюдать за закрытием канала. Закрытие каналов и выход из воркеров имеет больше смысла, когда приложение будет продолжать запускать несколько пакетов воркеров с течением времени — если мы не уведомим их о закрытии, а затем создадим больше воркеров позже, это приведет к утечке памяти, как эти воркеры продолжать ждать ввода, который никогда не придет. С другой стороны, нет ничего необычного в использовании одного и того же набора рабочих процессов для нескольких пакетов запросов.
person
Daniel Farrell
schedule
31.10.2020
for { ... }
, когда условие выхода из цикла требует слишком много команд, чтобы их можно было легко поместить в условное выражениеfor
. Менее уместна идея создания канала в цикле, который содержит только одно значение. Если ваша горутина всегда возвращает только одно значение, наличие этого возвращаемого значения является достаточным признаком того, что горутина выполнена. По возможности повторно используйте каналы и не закрывайте их, если вы хотите отправить больше данных позже. Очевидно, что в вашем случае горутина все равно бесполезна, и только в образовательных целях - person Daniel Farrell   schedule 31.10.2020