Идиоматические Голангские горуты
В Go, если у нас есть тип с методом, который запускает некоторый циклический механизм (опрос A и ведение B навсегда), лучше всего выразить это как:
// Run does stuff, you probably want to run this as a goroutine
func (t Type) Run() {
// Do long-running stuff
}
и документируйте, что это, вероятно, хочет быть запущено как goroutine (и пусть вызывающий абонент справляется с этим)
Или, чтобы скрыть это от вызывающего:
// Run does stuff concurrently
func (t Type) Run() {
go DoRunStuff()
}
Я новичок в Go и не уверен, что если соглашение говорит, пусть префикс вызывающего абонента имеет "go" или делает это для них, когда код предназначен для запуска async.
Мое текущее мнение заключается в том, что мы должны документировать и предоставлять вызывающему абоненту выбор. Я думаю, что в Go concurrency на самом деле не часть открытого интерфейса, а свойство его использования. Правильно ли это?
Ответы
Ответ 1
У меня было ваше мнение об этом, пока я не начал писать адаптер для веб-службы, который я хочу сделать одновременно. У меня есть обычная процедура, которая должна быть запущена для анализа результатов, которые возвращаются на канал из веб-вызовов. Нет абсолютно никакого случая, когда этот API работал бы без его использования в качестве подпрограммы.
Затем я начал рассматривать такие пакеты, как net/http. В этом пакете есть обязательный concurrency. На уровне интерфейса документировано, что он должен быть доступен одновременно, однако по умолчанию реализации автоматически используют подпрограммы go.
Поскольку стандартная библиотека Go обычно запускает подпрограммы go в своих собственных пакетах, я думаю, что если ваш пакет или API гарантирует это, вы можете обрабатывать их самостоятельно.
Ответ 2
Мое текущее представление заключается в том, что мы должны документировать и предоставлять вызывающему абоненту выбор.
Я с тобой согласен.
Так как Go делает так легко запускать код одновременно, вы должны стараться избегать concurrency в вашем API (который заставляет клиентов использовать его одновременно). Вместо этого создайте синхронный API, а затем у клиентов есть возможность запускать его синхронно или одновременно.
Это обсуждалось в разговоре пару лет назад: Twelve Go Best Practices
Слайд 26, в частности, показывает код больше как ваш первый пример.
Я рассматривал бы пакет net/http
как исключение, потому что в этом случае concurrency является почти обязательным. Если пакет не использовал concurrency внутренне, клиентский код почти наверняка должен был бы. Например, http.Client
не знает (насколько мне известно) запуск любых goroutines. Это делает только сервер.
В большинстве случаев это будет одна строка кода для вызывающего абонента в любом случае:
go Run()
или StartGoroutine()
Синхронный API не сложнее использовать одновременно и дает абоненту больше опций.
Ответ 3
Нет "правильного" ответа, потому что обстоятельства различаются.
Очевидно, бывают случаи, когда API может содержать утилиты, простые алгоритмы, коллекции данных и т.д., которые выглядели бы странно, если бы они были упакованы как goroutines.
И наоборот, бывают случаи, когда естественно ожидать "под капотом" concurrency, например, богатую библиотеку IO (пример HTTP-сервера является очевидным).
В более экстремальном случае подумайте, что вы должны создавать библиотеку параллельных сервисов plug-n-play. Такой API состоит из модулей, каждый из которых имеет хорошо описанный интерфейс через каналы. Очевидно, что в этом случае он неизбежно будет включать в себя гортани, начиная с API.
Одним ключом может быть наличие или отсутствие каналов в параметрах функции. Но я ожидал бы четкой документации о том, чего ожидать в любом случае.