Грядущий релиз версии 1.11 языка программирования Go принесет экспериментальную поддержку модулей — новую систему управления зависимостями для Go. (прим.перев.: релиз состоялся)
Недавно я уже писал об этом небольшой пост. С тех пор кое-что слегка поменялось, да и мы стали ближе к релизу, поэтому мне кажется, что настало время для новой статьи — добавим больше практики.
Итак, вот, что мы будем делать: создадим новый пакет и потом сделаем несколько релизов, чтобы посмотреть, как это работает.
Создание модуля
Первым делом создадим наш пакет. Назовём его "testmod". Важная деталь: каталог пакета следует разместить за пределами вашего $GOPATH
, потому что, внутри него по умолчанию отключена поддержка модулей. Модули Go — это первый шаг к полному отказу в будущем от $GOPATH
.
$ mkdir testmod
$ cd testmod
Наш пакет весьма прост:
package testmod
import "fmt"
// Hi returns a friendly greeting
func Hi(name string) string {
return fmt.Sprintf("Hi, %s", name)
}
Пакет готов, но он ещё пока не является модулем. Давайте исправим это.
$ go mod init github.com/robteix/testmod
go: creating new go.mod: module github.com/robteix/testmod
У нас появился новый файл с именем go.mod
в каталоге пакета со следующим содержимым:
module github.com/robteix/testmod
Немного, но именно это и превращает наш пакет в модуль.
Теперь мы можем запушить этот код в репозиторий:
$ git init
$ git add *
$ git commit -am "First commit"
$ git push -u origin master
До сих пор, любой желающий использовать наш пакет применил бы go get
:
$ go get github.com/robteix/testmod
И эта команда принесла бы самый свежий код из ветки master
. Такой вариант все ещё работает, но лучше бы нам так больше не делать, ведь теперь "есть способ лучше". Забирать код прямо из ветки master
, по сути, опасно, поскольку мы никогда не знаем наверняка, что авторы пакета не сделали изменений, которые "сломают" наш код. Для решения именно этой проблемы и были придуманы модули Go.
Небольшое отступление о версионировании модулей
Модули Go — версионируемы, плюс есть некоторая специфичность отдельных версий. Вам придется познакомится с концепциями, лежащими в основе семантического версионирования.
К тому же, Go использует метки репозитория, когда ищет версии, а некоторые версии отличаются от остальных: например, версии 2 и более должны иметь другой путь импорта, чем для версий 0 и 1 (мы дойдем до этого).
По умолчанию, Go загружает самую свежую версию, имеющую метку, доступную в репозитории.
Это важная особенность, поскольку её можно использовать при работе с веткой master
.
Для нас сейчас важно то, что, создавая релиз нашего пакета, нам необходимо поставить метку с версией в репозитории.
Давайте это и сделаем.
Делаем свой первый релиз
Наш пакет готов и мы можем "зарелизить" его на весь мир. Сделаем это с помощью версионных меток. Пусть номер версии будет 1.0.0:
$ git tag v1.0.0
$ git push --tags
Эти команды создают метку в моём Github-репозитории, помечающую текущий коммит как релиз 1.0.0.
Go не настивает на этом, но хорошей идеей будет создать дополнительно новую ветку ("v1"), в которую мы можем отправлять исправления.
$ git checkout -b v1
$ git push -u origin v1
Теперь мы можем работать в ветке master
не беспокоясь, что можем сломать наш релиз.
Использование нашего модуля
Давайте используем созданный модуль. Мы напишем простую программу, которая импортирует наш новый пакет:
package main
import (
"fmt"
"github.com/robteix/testmod"
)
func main() {
fmt.Println(testmod.Hi("roberto"))
}
До сих пор, вы запускали бы go get github.com/robteix/testmod
, чтобы скачать пакет, но с модулями становится интереснее. Для начала нам надо включить поддержку модулей в нашей новой программе.
$ go mod init mod
Как вы наверняка и ожидали, исходя из прочитанного ранее, в каталоге появился новый файл go.mod
с именем модуля внутри:
module mod
Ситуация становится ещё интереснее, когда мы попытаемся собрать нашу программу:
$ go build
go: finding github.com/robteix/testmod v1.0.0
go: downloading github.com/robteix/testmod v1.0.0
Как видно, команда go
автоматически нашла и загрузила пакет, импортируемый нашей программой.
Если мы проверим наш файл go.mod
, мы увидим, что кое-что изменилось:
module mod
require github.com/robteix/testmod v1.0.0
И у нас появился ещё один новый файл с именем go.sum
, который содержит хэши пакетов, чтобы проверять правильность версии и файлов.
github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA=
github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8=
Делаем релиз релиз с исправлением ошибки
Теперь, скажем, мы нашли проблему в нашем пакете: в приветствии отсутствует пунктуация!
Некоторые люди взбесятся, ведь наше дружелюбное приветствие уже не такое и дружелюбное.
Давайте исправим это и выпустим новую версию:
// Hi returns a friendly greeting
func Hi(name string) string {
- return fmt.Sprintf("Hi, %s", name)
+ return fmt.Sprintf("Hi, %s!", name)
}
Мы сделали это изменение прямо в ветке v1
, потому что оно не имеет отношения к тому, что мы будет делать дальше в ветке v2
, но в реальной жизни, возможно, вам следовало бы внести эти изменения в master
и уже потом бэкпортировать их в v1
. В любом случае, исправление должно оказаться в ветке v1
и нам надо отметить это как новый релиз.
$ git commit -m "Emphasize our friendliness" testmod.go
$ git tag v1.0.1
$ git push --tags origin v1
Обновление модулей
По умолчанию, Go не обновляет модули без спроса. "И это хорошо", поскольку нам всем хотелось бы предсказуемости в наших сборках. Если бы модули Go обновлялись бы автоматически каждый раз, когда выходит новая версия, мы вернулись бы в "тёмные века до-Go1.11". Но нет, нам надо сообщить Go, чтобы он обновил для нас модули.
А сделаем мы это с помощью нашего старого друга — go get
:
запускаем
go get -u
, чтобы использовать последний минорный или патч- релиз (т.е. команда обновит с 1.0.0 до, скажем, 1.0.1 или до 1.1.0, если такая версия доступна)
запускаем
go get -u=patch
чтобы использовать последнюю патч-версию (т.е. пакет обновится до 1.0.1, но не до 1.1.0)
запускаем
go get package@version
, чтобы обновиться до конкретной версии (например,github.com/robteix/testmod@v1.0.1
)
В этом списке нет способа обновиться до последней мажорной версии. На то есть весомая причина, как мы вскоре увидим.
Поскольку наша программа использовала версию 1.0.0 нашего пакета и мы только что создали версию 1.0.1, любая из следующих команд обновит нас до 1.0.1:
$ go get -u
$ go get -u=patch
$ go get github.com/robteix/testmod@v1.0.1
После запуска (допустим, go get -u
), наш go.mod
изменился:
module mod
require github.com/robteix/testmod v1.0.1
Мажорные версии
В соответствии со спецификацией семантического версионирования, мажорная версия отличается от минорных. Мажорные версии могут ломать обратную совместимость. С точки зрения Go модулей, мажорная версия — это совершенно другой пакет.
Может и звучит дико поначалу, но это имеет смысл: две версии библиотеки, которые несовместимы между собой, являются двумя разными библиотеками.
Давайте сделаем мажорное изменение в нашем пакете. Допустим, со временем, нам стало ясно, что наш API слишком прост, слишком ограничен для "юзкейсов" наших пользователей, поэтому нам надо изменить функцию Hi()
, чтобы она принимала язык приветствия в качестве параметра:
package testmod
import (
"errors"
"fmt"
)
// Hi returns a friendly greeting in language lang
func Hi(name, lang string) (string, error) {
switch lang {
case "en":
return fmt.Sprintf("Hi, %s!", name), nil
case "pt":
return fmt.Sprintf("Oi, %s!", name), nil
case "es":
return fmt.Sprintf("?Hola, %s!", name), nil
case "fr":
return fmt.Sprintf("Bonjour, %s!", name), nil
default:
return "", errors.New("unknown language")
}
}
Существующие программы, использующие наш API, сломаются, потому что они а) не передают язык в качестве параметра и б) не ожидают возврата ошибки. Наш новый API более не совместим с версией 1.x, так что встречайте версию 2.0.0.
Ранее я упоминал, что некоторые версии имеют особенности, и вот сейчас такой случай.
Версии 2 и более должны сменить путь импорта. Теперь это разные библиотеки.
Мы сделаем это добавив новый версионный путь к названию нашего модуля.
module github.com/robteix/testmod/v2
Всё остальное то же самое: пушим, ставим метку, что это v2.0.0 (и опционально содаём ветку v2)
$ git commit testmod.go -m "Change Hi to allow multilang"
$ git checkout -b v2 # optional but recommended
$ echo "module github.com/robteix/testmod/v2" > go.mod
$ git commit go.mod -m "Bump version to v2"
$ git tag v2.0.0
$ git push --tags origin v2 # or master if we don't have a branch
Обновление мажорной версии
Даже при том, что мы зарелизили новую несовместимую версию нашей библиотеки, существующие программы не сломались, потому что они продолжают исполользовать версию 1.0.1.
go get -u
не будет загружать версию 2.0.0.
Но в какой-то момент, я, как пользователь библиотеки, могу захотеть обновиться до версии 2.0.0, потому что, например, я один из тех пользователей, которым нужна поддержка нескольких языков.
Чтобы обновиться, надо соответствующим образом изменить мою программу:
package main
import (
"fmt"
"github.com/robteix/testmod/v2"
)
func main() {
g, err := testmod.Hi("Roberto", "pt")
if err != nil {
panic(err)
}
fmt.Println(g)
}
Теперь, когда я запущу go build
, он "сходит" и загрузит для меня версию 2.0.0. Обратите внимание, хотя путь импорта теперь заканчивается на "v2", Go всё ещё ссылается на модуль по его настоящему имени ("testmod").
Как я уже говорил, мажорная версия — это во всех отношениях другой пакет. Эти два модуля Go никак не связаны. Это значит, что у нас может быть две несовместимые версии в одном бинарнике:
package main
import (
"fmt"
"github.com/robteix/testmod"
testmodML "github.com/robteix/testmod/v2"
)
func main() {
fmt.Println(testmod.Hi("Roberto"))
g, err := testmodML.Hi("Roberto", "pt")
if err != nil {
panic(err)
}
fmt.Println(g)
}
И это избавляет от распространенной проблемы с управлением зависимостями, когда зависимости зависят от разных версий одной и той же библиотеки.
Наводим порядок
Вернёмся к предыдущей версии, которая использует только testmod 2.0.0 — если мы сейчас проверим содержимое go.mod
, мы кое-что заметим:
module mod
require github.com/robteix/testmod v1.0.1
require github.com/robteix/testmod/v2 v2.0.0
По умолчанию, Go не удаляет зависимости из go.mod
, пока вы об этом не попросите. Если у вас есть зависимости, которые больше не нужны, и вы хотите их почистить, можно воспользоваться новой командой tidy
:
$ go mod tidy
Теперь у нас остались только те зависимости, которые мы реально используем.
Вендоринг
Модули Go по умолчанию игнорируют каталог vendor/
. Идея в том, чтобы постепенно избавиться от вендоринга1. Но если мы все ещё хотим добавить "отвендоренные" зависимости в наш контроль версий, мы можем это сделать:
$ go mod vendor
Команда создаст каталог vendor/
в корне нашего проекта, содержащий исходный код всех зависимостей.
Однако, go build
по умолчанию все ещё игнорирует содержимое этого каталога. Если вы хотите собрать зависимости из каталога vendor/
, надо об этом явно попросить.
$ go build -mod vendor
Я предполагаю, что многие разработчики, желающие использовать вендоринг, будут запускать go build
, как обычно, на своих машинах и использовать -mod vendor
на своих CI.
Повторюсь, модули Go уходят от идеи вендоринга к использованию прокси для модулей для тех, кто не хочет напрямую зависеть от вышестоящих служб контроля версий.
Есть способы гарантировать, что go
будет недоступна сеть (например, с помощью GOPROXY=off
), но это уже тема следующей статьи.
Заключение
Статья кому-то может показаться сложноватой, но это из-за того, что я попытался объяснить многое разом. Реальность же такова, что модули Go сегодня в целом просты — мы, как обычно, импортируем пакет в наш код, а остальное за нас делает команда go
. Зависимости при сборке загружаются автоматически.
Модули также избавляют от необходимости в $GOPATH
, которая была камнем преткновения для новых разработчиков Go, у кого были проблемы с пониманием, почему надо что-то положить в какой-то конкретный каталог.
Вендоринг (неофициально) объявлен устаревшим в пользу использования прокси.1
Я могу сделать отдельную статью про прокси для Go модулей.
Примечания:
1 Я думаю, что это слишком громкое выражение и у некоторых может остаться впечатление, что вендоринг убирают прямо сейчас. Это не так. Вендоринг все ещё работает, хотя и слегка по другому, чем раньше. По-видимому, есть желание заменить вендоринг чем-то лучшим, например, прокси (не факт). Пока это просто стремление к лучшему решению. Вендоринг не уйдет, пока не будет найдена хорошая замена (если будет).
Комментарии (39)
memba
27.08.2018 16:20$ go mod init mod
Почему не так: go init
Инициализировали пакет, создался файл go.package.
Всё, работаем с пакетом и дальше. Зачем вводить какую-то новую сущность…kilgur Автор
27.08.2018 16:43М.б. потому что модули — пока фича экспериментальная и с особенностями. И да, go обновился с 1.10 до 1.11 — ломать ничего нельзя, только добавлять.
QtRoS
27.08.2018 20:46Правильно я понимаю, что выкачать все зависимости в папку vendor теперь моветон? И чтобы в жёсткой корпоративной среде себя от роском… сбоев гитхаба нужно понимать локальное хранилище а-ля гитлаб?
kilgur Автор
27.08.2018 21:04-1Ещё пока не моветон. Отказ от вендоринга — это мнение автора. В официальных доках я такого нигде не встречал. Просто у модулей "ноги растут" из vgo, который "детище" Пайка. Если модули "зайдут" (imho, не если, а когда), vgo забросят, а с ним и остальные инструменты вендоринга станут неправославными. В общем, судя по активности, модули и wasm — это тренд, но пока ещё далеко не мэйнстрим (три дня назад всего релиз был).
Edison
27.08.2018 23:59+1Просто у модулей "ноги растут" из vgo, который "детище" Пайка.
Нет, vgo это прототип от Russ Cox.
Если модули "зайдут" (imho, не если, а когда), vgo забросят, а с ним и остальные инструменты вендоринга станут неправославными.
Так модули в Go это и есть vgo — его Расс сделал и предложил включить в Go.
Не путайте людей.
kilgur Автор
28.08.2018 08:03Нет, vgo это прототип от Russ Cox.
Точно. Это я попутал. Спасибо за исправление.
Так модули в Go это и есть vgo — его Расс сделал и предложил включить в Go.
Вот, тут не соглашусь. Да, разработку перенесли в основной репозиторий. Да, изменения зеркалируют в репо vgo (пока). Но все-таки это разные продукты. Можно в модулях убрать вендоринг, а в vgo оставить. Ну, и так далее… Слияния/поглощения не было, значит, это разные продукты.Edison
28.08.2018 08:45Изменения не зеркалируют
The code in this repo is auto-generated from and should behave exactly like the Go 1.11 go command, with two changes:
It behaves as if the GO111MODULE variable defaults to on.
When using a Go 1.10 toolchain, go vet during go test is disabled.Edison
28.08.2018 09:12я имею ввиду, что коммиты не зеркалируются, vgo — это
go mod
в отдельном репозитории. Сделано это для go 1.10 пользователей. Не думаю, что будут как-то отдельно развивать vgo, смысла нету две код базы поддерживать.kilgur Автор
28.08.2018 10:44Слово «зеркалировать» я взял тут:
Currently, module support is in active development in the main go repository, with changes mirrored back to the vgo repository.
. Перевести можно по-разному, но смысл в том, что это не backport.
Посмотрел в репозиторий: 19 июля и 8 августа были коммиты rsc с комментарием «import from main repo ...». В основном репо есть коммиты по функционалу модулей (тоже от rsc), например, 21 августа (старее не смотрел). Т.е. на текущий момент устанавливая vgo вы получаете чуточку не то, что есть в основном репо… пока Russ снова не зальёт изменения.
Я к тому, что лучше относится к vgo и модулям как к разным штукамEdison
28.08.2018 14:26Нет, я не буду использовать vgo, у меня уже go 1.11. vgo только для тех, кто хочет использовать модули в go 1.10.
Edison
27.08.2018 23:55нет, типа гитлиба не надо, есть уже github.com/gomods/athens — подымаешь у себя прокси athens, указываешь в GOPROXY и все ок
istepan
28.08.2018 07:17Все еще недоумеваю почему они сразу так не сделали?!
Ждем статью про GOPROXY.
SirEdvin
Эм… А есть ли смысл в этой странной сомнительной фичи, кроме призрачной возможности использовать несколько несовместимых версий одного пакета, что все равно в общем случае скорее всего будет невозможно или очень затратно из-за инициализаций и прочего?
Ну и да, надеяться на то, что все будут придерживаться semver это как-то очень наивно.
kilgur Автор
На мой взгляд это такая двойная перестраховка от того, чтобы случайно не обновить мажорную версию. Ну, то есть `go get`, конечно, бдит, но на всякий случай, пусть и пути импорта для мажорных версий различаются. Парни готовятся к Go 2.0
Ну, и, теоретически, возможность использовать разные мажорные версии в одном бинарнике может быть и востребована — например, если новая версия пакета для базы данных работает быстрее, но ломается на больших объемах. Т.е. понятно, что отсюда следуется, что новая версия — сырая, и тащить её в продакшен «не айс», но иногда хочется, а иногда и приходится. И оставить в коде возможность переключаться между версиями (например, флагом) без замены бинарника, на мой взгляд, имеет смысл. Правда, в очень редких и недолгих случаях, imho, п.ч. поддерживать в коде две разные версии пакета может оказаться очень затратным делом…
А почему надеятся на то, что все будут придерживаться semver — это наивно? Я за последние пару лет как-то не сталкивался с «экзотикой».
SirEdvin
Например, вот эта репа, правда, она заброшена. Или вот gorm. А еще куча реп вообще без тегов.
Практика других языков показывает, что довольно мало разработчиков правильно понимают semver (например, ломая совместимость в пачтах) и обычно, те кто понимают — это хорошо организованные команды. А все остальные порождают страдания. Очень сомнительно, что программисты на go внезапно другие.
На мой взгляд это настолько сомнительная практика, что ее совсем не стоит использовать. В том же примере с базой данных, мне кажется, что существенная часть магических проблем будет сосредоточена с тем, что обе либы пытаются работать с базой одновременно и никак не синхронизированы.
Касательно оставлять возможность переключать флагом — это всегда настолько затратно и усложняет код (особенно в Go, где плохо со всякими enterprice-шаблонами типо стратегий и DI), что на практике разработчикам конечных приложений практически никогда не стоит свеч.
kilgur Автор
Мне кажется, стремиться всегда надо к лучшему. И криворукие программисты тоже будут всегда. Поэтому идеала не существует, но если к нему не стремиться, то лучше не станет. imho, semver уже стандарт.
Я себе одновременную работу разных версий не представлял совсем. Т.е. если это
iris.v5
иiris.v6
, то на разных портах, как минимум.Не спорю, но если «фича» досталась бесплатно, зачем от неё отказываться? Переименование импорта в наличии давно, плюс требование мажорную версию импортировать с другим путём (оставим за скобкой причины), равно возможность использовать разные мажорные версии пакета одновременно.
Делать специально возможность, которая, возможно, нафик ни кому не сдалась — это оверинжиниринг. А бесплатно получить фичу, которая не планировалась, но получилась — почему бы и нет? Её выкидывать затратнее, чем оставить
SirEdvin
Ну, я правда не смог придумать случай, где бы это нужно и безопасно, к сожалению)
Тут вы правы, разумеется)
VolCh
Часто наоборот бывает: затратно переключать проект, в котором используется какая-то старая либа в каких-то уголках проекта, в которые годами никто не заглядывал. А тут новая задача заходит, которая идеально ложится на новую версию либы. А годами забытые уголки часто и тестами не покрыты, или покрыты так, что лучше бы не были покрыты вообще. Тут патч проапдейдить не дают «у нас это баг проявляется? нет? значит не трогаем», не то что на пару мажорных версий обновить.
SirEdvin
Я уже представил, что надо будет держать в голове несколько разных версий одной либы и помнить, где какая используется и мне стало очень печально.
Вы в самом деле хотите такое попробовать?
VolCh
В том-то и дело, что не надо. Нужна новая фича для новой задачи, которая есть только в новой версии либы: добавляем новую версию и релизим фичу, не перелопачивая весь проект в рамках задачи на новую фичу. Куда-то в бэклог добавляем задачу перевести весь проект на новую версию.
SirEdvin
Обычно это означает, что у вас неопределенное (обычно, очень продолжительное время) будет использоваться две версии библиотеки, и программисты, которые выучили новую версию будут использовать ее, а другие тулить везде старую.
Так себе идея, на мой взгляд. Я бы старался такого избегать в 100% случаев
VolCh
Ну дополнительно запрещается писать новый код со старой версией. Как, например, в случае принятия новых правил форматирования кода или использования каких-то паттернов (в широком смысле слова).
Edison
Вы опять не разобрались в причинах, почему так сделали.
У вас есть зависимость libA-2.0 и libB-1.0, но у libB есть зависимость — libA-1.0. libA-1.0 и 2.0 не совместимы. Допустим должна быть всегда только одна версия либы (libA-2.0) — тогда сломается зависимость libB-1.0. С go mod все будет работать.
И вот уже в которой раз Вы приходите в пост про Go и опять только претензии, и опять не понимаете суть.
Кстати, Вы уже разобрались с многопоточностью, goroutine и каналами?
SirEdvin
Я скажу проще — это не очень полезная и опасная фича. Очень наивно предполагать, что в общем случае две разные версии пакета не будут конфликтовать между собой. Какие-то простые либы вполне возможно, но держать в рантайме два пула коннекшенов к базе данных?
Я понимаю, какую проблему решает это решение, но на мой взгляд решение привносит еще более страшную проблему.
Я хотел вам там написать, что в моем мире синхронизация между потоками нужна для исчезающего количества задач, потому что обычно начинают заниматься более глобальным масштабированием, а утилизация 100% ядер обычно означает, что у вас что-то не так, но мне было лень.
Я понял ваши аргументы, но опять же, с моей точки зрения они не настолько сильны, хотя бы потому, что запуск нескольких интерпретаторов питона не такой дорогой, а 30кб я могу себе позволить.
Ну и да, оверхед на рантайм и скорость работы — это не единственные критерии языка. Я вот смотрю на некоторых чатботов на go и мне хочется плакать от реализации и скорости поддержки. Но они очень производительны, да.
Edison
А что может быть страшнее, чем сломанные зависимости? Опять же, это очень специфический кейс, но даже если так — с go mod все будет работать.
Что это за библиотека, которая автоматом создает пул коннектов при импорте? Выбросите ее.
Если у вас такой мир, забудьте про Go и живите спокойно, хватит приходит в каждый пост про Go, он не для вас.
Хотя про утилизацию ресурсов вы так и не поняли.
SirEdvin
Не инициализированных? Практически любая для работы с бд, разве нет?
В любом случае, возможно, я не правильно сказал, но смысл был в том, что крупные библиотеки, которые в самом деле сложно заменить обычно имеют какой-то свой runtime и странную логику внутри, а иногда даже рассчитывать, что она одна такая в приложении, что значит потенциальные проблемы при использовании двух версий одновременно, а мелкие вполне можно просто быстро заменить.
Использование двух версий либы для сериализации с разными нотациями и несовместимыми структурами? Это как сломанные зависимости, но наоборот. Причем, если этот подход будет популярным, будем как в javascript, когда они пилят костыли, что бы в js-bundle не подтягивались абсолютно все версии lodash, которые существуют.
Я так же добавлю, что вполне вероятно, что разработчики какой-то крупной библиотеки немного долбанутся и будут выпускать мажорку каждые полгода, как это внезапно решили сделать ребята из gnome.
Я просто захожу в каждый пост на хабре, правда.
Возможно, я не прав, но для меня шедулер, который вы описали очень полезен только в случае, если задача сразу и cpu-bound, и io-bound и по какой-то причине вы ее не разделили. Это круто, но в моем случае простое деление задач на cpu-bound части (где хватает синхронных воркеров с асинхронным мастером) и io-bound (просто асинхронные воркеры) в целом покрывает большое количество кейс. В остальных нужно страдать, давить ресурсами ну или использовать черную магию/перенос задачи на базу данных/rust/c++
Edison
Нет, как раз это очень важно для io-bound задач.
Вот есть у вас веб сервер и 10 тредов, каждый реквест обрабатывается одним тред, при этом это io-bound задача — то есть будет момент когда тред будет полностью заблокирован на IO. В Go же не будет такого момента, когда тред будет заблокирован — goroutine будет заблокирована, но треды нет — они будут обрабатывать другие горутины (горутина — один http запрос, читай другие запросы).
Допустим длительность обработки одного запроса одинаковая c Go и c xxxx (python, java, etc). В таком случае Go обработает больше запросов за один и тот же промежуток времени.
SirEdvin
Для чисто io-bound задач есть асинхронное программирование, которое позволяет проворачивать такие же вещи.
Например, в python есть asyncio с event loop, который позволяет обрабатывать и отправлять одному потоку довольно большое количество запросов одновременно.
Разница только в том, что это все привязано к одному треду. То есть это менее оптимально, но в случае веб-сервиса обычно перед вашим приложением запускается master, который делает примитивный round-robin или что-то более сложное, организовывая распределение нагрузки.
Edison
Один поток будет обрабатывать одновременно только один запрос.
Почитайте все таки про многопоточное и конкурентное программирование. Особенно в Go — горутины, каналы и так далее.
SirEdvin
Возможно я чего-то не понимаю, давайте я попробую объяснить, что я имею ввиду на примере:
Вот этот хендлер будет обрабатывать несколько запросов одновременно. Разумеется, в конкретный момент времени будет выполнятся одна инструкция, но во время того, как будет выполнятся запрос к базе тред не будет заблокирован, а просто будет выполнять другую корутину. И это означает, что несколько запросов будут обрабатываться параллельно, я не прав?
Edison
Вы не правы, если у вас один тред (как вы сами сказали), выполняться будут конкурентно, не параллельно, просто тред не будет ждать i/o и в это время обрабатывать другой запрос.
Почитайте про многопоточность и конкурентность и не сравнивайте балансировку нагрузки с этим, и каналы в Go и очередями.
SirEdvin
Да, вы правы, я использовал не правильный термин, но имел ввиду именно то, что вы описали.
Я имел ввиду, что конкретно для веб приложения асинхронность + балансировка нагрузки вполне рабочее решение. Это еще один способ реализовать именно параллельную работу, который довольно широко применяется. Я понимаю, что эффективность такого решения скорее всего, значительно хуже в большинстве случаев, но, как я уже говорил выше — эффективность не единственный критерий.
Касательно каналов, как часто вы используете их не как очередь и не из-за того, что у вас недостаток контроля над горутинами?
Edison
Каналы использую для коммуникации (в основном) и управления (старт-стоп).
Вы разницу понимаете между каналами в Го внешней очередью?
SirEdvin
Ну, думаю, как минимум в том, что каналы go работаю в рамках одного процесса, что делает их значительно быстрее внешней очереди. Или есть еще какое-то принципиальное отличие, которое я упустил?
Edison
ну так как вы можете это сравнивать? Так же как сравниваете горутины и процессы питона.
Вы сравниваете многопоточное приложения с практически распределенными системами. Это разные уровни.
Точно так же сервисы на Go будет использовать внешнею очередь для коммуникации между сервисами.