@wargych

Правильное использование каналов (effective go)?

Читаю Effective Go, раздел каналы. Вот что я не понимаю, есть код:
var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    process(r)  // May take a long time.
    <-sem       // Done; enable next request to run.
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)  // Don't wait for handle to finish.
    }
}

Далее текст:
Данный дизайн имеет проблемы: Serve создает новую горутину для каждого входящего запроса, при этом будет запущено не более MaxOutstanding в один момент. Если количество запросов увеличивается слишком быстро, то как результат, программа может потребовать бесконечное количество ресурсов.
.
Вопрос 1 - на что может уйти бесконечное количество ресурсов, если количество горутин мы можем регулировать через параметр MaxOutstanding - на очередь запросов в заблокированный канал?
Далее:
Мы можем решить это изменением Serve используя изменения количества порождаемых горутин. Вот очевидное решение, но будьте осторожны, так как оно имеет ошибку, которую позже исправим:

func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func() {
            process(req) // Buggy; see explanation below.
            <-sem
        }()
    }
}

Вопрос 2 - в чем принципиальная разница? В первом случае получали запрос в канал запросов, отправляли запрос в обработчик и, используя буферизированный канал как семафор для ограничения количества горутин по MaxOutstanding, запускали некий процесс, передавая в него запрос.
Во втором случае - уже без обработчика, перебираем канал запросов, присваиваем запрос в req, открываем семафор, запускаем горутину с выполнением процесса с передачей туда запроса, из горутины закрываем семафор. Не вижу, в чем должна быть разница в результате?
Вопрос 3 - апд. - уже понял, вопрос был задан по невнимательности.
Идем дальше:
Ошибка в том, что в языке Go цикл for, цикл переменной повторно используется для каждой итерации, так что переменные req разделяется по всем горутинам. Это не то что мы хотим. Нам нужно убедиться, что req является уникальной для каждой горутины. Вот один из способов, передавать значение req в качестве аргумента для закрытии горутины:

func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func(req *Request) {
            process(req)
            <-sem
        }(req)
    }
}

Вопрос 4 - опять же, какая разница в результате?
Что за дичь в тексте с разделением req по всем горутинам? В моем понимании, получили в ходе итерации req - вызвали горутину, новый req - новая горутина, в чем проблема?
В общем, вопросов много, просьба прояснить что-как :))
  • Вопрос задан
  • 822 просмотра
Решения вопроса 3
@PapaStifflera
Родился, вырос...
Все ответы на ваши вопросы есть здесь: https://www.ozon.ru/context/detail/id/34671680/
Ответ написан
Комментировать
@rustler2000
погромист сикраш
По первому, наверное ты сам догадался, что горутины стартуют до того как семафор ожидается, потому поправили.

По второму куску вот тут внятный ответ - https://github.com/golang/go/wiki/CommonMistakes#u...
Ответ написан
Комментировать
@wargych Автор вопроса
Спасибо за ответы!
Ну и если кому понадобится:
Вопрос 1: да, переполнение памяти может произойти из-за очереди УЖЕ ЗАПУСТИВШИХСЯ и ожидающих канал горутин.
Вопросы 2, 4: в примере к вопросу 2 переменная, которая не передавалась в функцию как параметр, но использовалась функцией, берется из внешней области замыкания - т.е. из области видимости цикла, который с очень большой долей вероятности к этому моменту уже перезапишет в переменную другое значение.
Раньше не доводилось сталкиваться с многопоточностью, отсюда, видимо, и вышло затруднение.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы