Pull to refresh

Comments 22

Не стоит изобретать новые велосипеды. Уже есть https://pkg.go.dev/golang.org/x/sync/errgroup

Не то чтобы семафоры были изобретены только что https://pkg.go.dev/golang.org/x/sync/semaphore

Вообще по стечению обстоятельств вот только что обсуждали мой код, где коллега убедительно убедил что использовать семафоры - хорошая затея. Код действительно стал проще.

Правда в моем кейсе на производительность наплевать, так что бонус лично для меня чисто эстетический

Джсоны перекладываем...

Достаем из сервис1 список ресурсов, и для каждого ресурса выполняем пайплайн в сервис2.
Пайплайн синхронный и состоит из нескольких api-вызовов (условно, resource1/check, resource1/init, resource1/update)

Чтобы не насиловать api сервис2, запускаем пайплайн на N ресурсов параллельно, если для какого-то ресурса пайплайн завершился - может запускать для следующего

Я не точно выразился, думал сразу будет понятно. Я не семафоры имел ввиду, а worker pool. Хотя, истины ради, и я писал свою реализацию.

Спасибо! Видел этот пакет, даже пользовался пару раз)

А мы пользуемся https://github.com/alitto/pond
Группировка, таймауты и много ещё чего.
Различные стратегии для наращивания воркеров.

Отличная статья! Есть пример для семафора, к сожалению он не затрагивает воркер пул, но думаю тк статья про семафор, то имеет место быть. Плюс, если кто-то захочет найти еще примеры использования семафора, то может посмотреть исходники pgx (а точнее pgxpool).

Когда я писал тесты для слоя базы данных(тестировал без мока бд). То была проблема с тем, что тесты ломали друг друга. Решений было несколько:
1. Ограничить количество одновременных тестов до 1 (т.к у нас не только бд тесты, то их выполнение на 1 потоке занимает много времени)
2. Создавать отдельные схемы для каждого тесты (усложнение логики)
3. Запускать для каждого теста отдельный контейнер
4. Использовать семафор с размером 1 для лока 1 теста бд за раз (плюсы: простая логика. Минусы, при большом количество тестов для базы данных скорее всего это станет узким горлом).
Собственно 4 вариант и был выбран. К слову, если кто-то знает еще варианты решения этого вопроса, то буду рад узнать, как минимум для понимания того, что я мог упустить.

Также было бы интересно посмотреть использование паттернов fan-in и fan-out на практике.

  1. Использовать семафор с размером 1 для лока 1 теста бд за раз

Что-то я не уловил. Разве этим самым вы не указываете, что тесты с БД должны выполняться последовательно? Но ведь тоже самое можно получить, просто не указывая для таких тестов

t.Parallel()

Или я чего-то не понял?

Тесты из разных пакетов буду запускаться в любом случае параллельно(только если не ограничить количество одновременных задач, см первый вариант), t.Parallel() указывается для параллельного запуска тестов из одного пакета или сабтестов. В этом и была проблема, у меня несколько пакетов, 1 за каждую таблицу. Если я где-то ошибаюсь, то буду рад если кто-то меня поправит.

>узким горлом

Лишь бы не бутылочным местом, пьянству бой!

А зачем плодить миллион висящих горутин?

for _, v := range usrs {
  sem.Acquire()
  go func(usr users.User) {
    defer sem.Release()
    ...
  }
}

Вот меня мучил тот же вопрос, когда я гуглил реализации семафоров когда-то. Поэтому когда я решал аналогичную задачу, я делал это именно вторым способом. Горутины нет смысла плодить бесконечно, это забьет ресурсы, поэтому для меня в тот момент было логичным ограничить как раз количество созданных горутин.

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

       select {
			case outputCh <- resultWithError{
				User: usr,
				Err:  err,
			}:
			case <-sgnlCh:
				return
		}

В этой конструкции sgnlCh должен идти первым в селекте, а не последним

А какая разница? Порядок обработки select-case не определен, если в обоих каналах есть данные

Вы правы, перемещение одного case вверх не изменит ситуацию. Мне изначально резануло глаз то, что первый case во всех случаях готов исполниться. А значит, даже если у нас есть сигнал на выход, нет никакой гарантии сразу завершить работу. Изначально я почему то думал, что первый case немного приоритетнее, но был не прав, извиняюсь.

при закрытии sgnlCh по факту не происходит уже чтения из outputCh. Это значит, что первый кейс (case outputCh <-) к моменту закрытия sgnlCh будет в блокировке всегда в данном примере.

Есть некое количество пользователей, например от 1 до 100 000, по каждому надо выполнить функцию Deactivate. Как правило, это происходит путём отправки одного запроса в сторонний API

Тогда ваша функция и этот внешний API должны принимать контекст и код обоих подходов будет выглядеть иначе.

Также смущают эти моменты:

  1. Сомнительный дизайн у cемафора. Если вы его выделяете в отдельную сущность (а не просто работаете с каналом), то очевидно канал лучше сделать приватным и обернуть его создание в конструктор.

  2. Вообще говоря подход с семафором и cancel-ом по первой ошибке реализован в golang.org/x/sync/errgroup и там это сделано почище — единственное чего там может не хватать recover-а паник внутри горутин (см. https://github.com/golang/go/issues/53757)

  3. Во втором примере у вас утечка горутин. После return-а по ошибке в основной функции горутины будут вечно висеть на записи в outCh. Это легко отловить через условный go.uber.org/goleak

  4. Тут уже мое имхо, но кажется, что лучше дожидаться завершения всех запущенных горутин в функции, даже если вы уверены, что они рано или поздно завершатся

Спасибо за развернутый комментарий! Особенно за то, что расписали по пунктам (это хорошо читается)

Согласен с моментом насчет контекста. Поскольку это обучающая статья, я убрал select'ы по контексту. Своим студентам, когда работал на курсе от МТС я давал пример с контекстами. Но здесь посчитал, что это перегрузит обучающую статью.

1. Сделал максимально простым, но читаемым (чтобы видно было, что "семафор"). Вообще необязательно даже отдельный тип делать, можно каналом ограничиться. Кстати, если у вас есть под рукой реализация такая которая нравится можете скинуть)

2. Согласен

3. Согласен. В бою я обычно вешаю контекст везде. В этом случае всегда будет кейс который снимает блокировку висящих горутин и завершает этот процесс.

4. Я так понял, это связано с 3-м: мы гарантируем, что за пределами функций не будет утечек. В таком случае согласен.

Sign up to leave a comment.