Грядущий релиз версии 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)


  1. SirEdvin
    27.08.2018 15:45

    Версии 2 и более должны сменить путь импорта. Теперь это разные библиотеки.

    Эм… А есть ли смысл в этой странной сомнительной фичи, кроме призрачной возможности использовать несколько несовместимых версий одного пакета, что все равно в общем случае скорее всего будет невозможно или очень затратно из-за инициализаций и прочего?


    Ну и да, надеяться на то, что все будут придерживаться semver это как-то очень наивно.


    1. kilgur Автор
      27.08.2018 16:05

      На мой взгляд это такая двойная перестраховка от того, чтобы случайно не обновить мажорную версию. Ну, то есть `go get`, конечно, бдит, но на всякий случай, пусть и пути импорта для мажорных версий различаются. Парни готовятся к Go 2.0

      Ну, и, теоретически, возможность использовать разные мажорные версии в одном бинарнике может быть и востребована — например, если новая версия пакета для базы данных работает быстрее, но ломается на больших объемах. Т.е. понятно, что отсюда следуется, что новая версия — сырая, и тащить её в продакшен «не айс», но иногда хочется, а иногда и приходится. И оставить в коде возможность переключаться между версиями (например, флагом) без замены бинарника, на мой взгляд, имеет смысл. Правда, в очень редких и недолгих случаях, imho, п.ч. поддерживать в коде две разные версии пакета может оказаться очень затратным делом…

      А почему надеятся на то, что все будут придерживаться semver — это наивно? Я за последние пару лет как-то не сталкивался с «экзотикой».


      1. SirEdvin
        27.08.2018 16:17
        +1

        А почему надеятся на то, что все будут придерживаться semver — это наивно? Я за последние пару лет как-то не сталкивался с «экзотикой».

        Например, вот эта репа, правда, она заброшена. Или вот gorm. А еще куча реп вообще без тегов.


        Практика других языков показывает, что довольно мало разработчиков правильно понимают semver (например, ломая совместимость в пачтах) и обычно, те кто понимают — это хорошо организованные команды. А все остальные порождают страдания. Очень сомнительно, что программисты на go внезапно другие.


        Ну, и, теоретически, возможность использовать разные мажорные версии в одном бинарнике может быть и востребована

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


        Касательно оставлять возможность переключать флагом — это всегда настолько затратно и усложняет код (особенно в Go, где плохо со всякими enterprice-шаблонами типо стратегий и DI), что на практике разработчикам конечных приложений практически никогда не стоит свеч.


        1. kilgur Автор
          27.08.2018 16:36

          Мне кажется, стремиться всегда надо к лучшему. И криворукие программисты тоже будут всегда. Поэтому идеала не существует, но если к нему не стремиться, то лучше не станет. imho, semver уже стандарт.

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

          Я себе одновременную работу разных версий не представлял совсем. Т.е. если это iris.v5 и iris.v6, то на разных портах, как минимум.

          Касательно оставлять возможность переключать флагом — это всегда настолько затратно и усложняет код (...), что на практике разработчикам конечных приложений практически никогда не стоит свеч.

          Не спорю, но если «фича» досталась бесплатно, зачем от неё отказываться? Переименование импорта в наличии давно, плюс требование мажорную версию импортировать с другим путём (оставим за скобкой причины), равно возможность использовать разные мажорные версии пакета одновременно.
          Делать специально возможность, которая, возможно, нафик ни кому не сдалась — это оверинжиниринг. А бесплатно получить фичу, которая не планировалась, но получилась — почему бы и нет? Её выкидывать затратнее, чем оставить


          1. SirEdvin
            27.08.2018 16:38

            Я себе одновременную работу разных версий не представлял совсем. Т.е. если это iris.v5 и iris.v6, то на разных портах, как минимум.

            Ну, я правда не смог придумать случай, где бы это нужно и безопасно, к сожалению)


            А бесплатно получить фичу, которая не планировалась, но получилась — почему бы и нет? Её выкидывать затратнее, чем оставить

            Тут вы правы, разумеется)


    1. VolCh
      27.08.2018 18:13
      +1

      Часто наоборот бывает: затратно переключать проект, в котором используется какая-то старая либа в каких-то уголках проекта, в которые годами никто не заглядывал. А тут новая задача заходит, которая идеально ложится на новую версию либы. А годами забытые уголки часто и тестами не покрыты, или покрыты так, что лучше бы не были покрыты вообще. Тут патч проапдейдить не дают «у нас это баг проявляется? нет? значит не трогаем», не то что на пару мажорных версий обновить.


      1. SirEdvin
        28.08.2018 11:37

        Я уже представил, что надо будет держать в голове несколько разных версий одной либы и помнить, где какая используется и мне стало очень печально.

        Вы в самом деле хотите такое попробовать?


        1. VolCh
          28.08.2018 12:27

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


          1. SirEdvin
            28.08.2018 12:33
            +1

            Обычно это означает, что у вас неопределенное (обычно, очень продолжительное время) будет использоваться две версии библиотеки, и программисты, которые выучили новую версию будут использовать ее, а другие тулить везде старую.

            Так себе идея, на мой взгляд. Я бы старался такого избегать в 100% случаев


            1. VolCh
              28.08.2018 12:37

              Ну дополнительно запрещается писать новый код со старой версией. Как, например, в случае принятия новых правил форматирования кода или использования каких-то паттернов (в широком смысле слова).


        1. Edison
          28.08.2018 14:25

          Вы опять не разобрались в причинах, почему так сделали.
          У вас есть зависимость 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 и каналами?


          1. SirEdvin
            28.08.2018 14:40

            Вы опять не разобрались в причинах, почему так сделали.
            У вас есть зависимость libA-2.0 и libB-1.0, но у libB есть зависимость — libA-1.0. libA-1.0 и 2.0 не совместимы. Допустим должна быть всегда только одна версия либы (libA-2.0) — тогда сломается зависимость libB-1.0. С go mod все будет работать.

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


            Я понимаю, какую проблему решает это решение, но на мой взгляд решение привносит еще более страшную проблему.


            Кстати, Вы уже разобрались с многопоточностью, goroutine и каналами?

            Я хотел вам там написать, что в моем мире синхронизация между потоками нужна для исчезающего количества задач, потому что обычно начинают заниматься более глобальным масштабированием, а утилизация 100% ядер обычно означает, что у вас что-то не так, но мне было лень.


            Я понял ваши аргументы, но опять же, с моей точки зрения они не настолько сильны, хотя бы потому, что запуск нескольких интерпретаторов питона не такой дорогой, а 30кб я могу себе позволить.


            Ну и да, оверхед на рантайм и скорость работы — это не единственные критерии языка. Я вот смотрю на некоторых чатботов на go и мне хочется плакать от реализации и скорости поддержки. Но они очень производительны, да.


            1. Edison
              28.08.2018 15:26

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

              Я понимаю, какую проблему решает это решение, но на мой взгляд решение привносит еще более страшную проблему.

              А что может быть страшнее, чем сломанные зависимости? Опять же, это очень специфический кейс, но даже если так — с go mod все будет работать.
              Что это за библиотека, которая автоматом создает пул коннектов при импорте? Выбросите ее.


              Я хотел вам там написать, что в моем мире синхронизация между потоками нужна для исчезающего количества задач, потому что обычно начинают заниматься более глобальным масштабированием, а утилизация 100% ядер обычно означает, что у вас что-то не так, но мне было лень.

              Если у вас такой мир, забудьте про Go и живите спокойно, хватит приходит в каждый пост про Go, он не для вас.
              Хотя про утилизацию ресурсов вы так и не поняли.


              1. SirEdvin
                29.08.2018 09:44

                Что это за библиотека, которая автоматом создает пул коннектов при импорте? Выбросите ее.

                Не инициализированных? Практически любая для работы с бд, разве нет?


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


                А что может быть страшнее, чем сломанные зависимости? Опять же, это очень специфический кейс, но даже если так — с go mod все будет работать.

                Использование двух версий либы для сериализации с разными нотациями и несовместимыми структурами? Это как сломанные зависимости, но наоборот. Причем, если этот подход будет популярным, будем как в javascript, когда они пилят костыли, что бы в js-bundle не подтягивались абсолютно все версии lodash, которые существуют.


                Я так же добавлю, что вполне вероятно, что разработчики какой-то крупной библиотеки немного долбанутся и будут выпускать мажорку каждые полгода, как это внезапно решили сделать ребята из gnome.


                Если у вас такой мир, забудьте про Go и живите спокойно, хватит приходит в каждый пост про Go, он не для вас.

                Я просто захожу в каждый пост на хабре, правда.


                Хотя про утилизацию ресурсов вы так и не поняли.

                Возможно, я не прав, но для меня шедулер, который вы описали очень полезен только в случае, если задача сразу и cpu-bound, и io-bound и по какой-то причине вы ее не разделили. Это круто, но в моем случае простое деление задач на cpu-bound части (где хватает синхронных воркеров с асинхронным мастером) и io-bound (просто асинхронные воркеры) в целом покрывает большое количество кейс. В остальных нужно страдать, давить ресурсами ну или использовать черную магию/перенос задачи на базу данных/rust/c++


                1. Edison
                  29.08.2018 11:56

                  Возможно, я не прав, но для меня шедулер, который вы описали очень полезен только в случае, если задача сразу и cpu-bound, и io-bound и по какой-то причине вы ее не разделили. Это круто, но в моем случае простое деление задач на cpu-bound части (где хватает синхронных воркеров с асинхронным мастером) и io-bound (просто асинхронные воркеры) в целом покрывает большое количество кейс. В остальных нужно страдать, давить ресурсами ну или использовать черную магию/перенос задачи на базу данных/rust/c++

                  Нет, как раз это очень важно для io-bound задач.
                  Вот есть у вас веб сервер и 10 тредов, каждый реквест обрабатывается одним тред, при этом это io-bound задача — то есть будет момент когда тред будет полностью заблокирован на IO. В Go же не будет такого момента, когда тред будет заблокирован — goroutine будет заблокирована, но треды нет — они будут обрабатывать другие горутины (горутина — один http запрос, читай другие запросы).
                  Допустим длительность обработки одного запроса одинаковая c Go и c xxxx (python, java, etc). В таком случае Go обработает больше запросов за один и тот же промежуток времени.


                  1. SirEdvin
                    29.08.2018 12:07

                    Для чисто io-bound задач есть асинхронное программирование, которое позволяет проворачивать такие же вещи.


                    Например, в python есть asyncio с event loop, который позволяет обрабатывать и отправлять одному потоку довольно большое количество запросов одновременно.


                    Разница только в том, что это все привязано к одному треду. То есть это менее оптимально, но в случае веб-сервиса обычно перед вашим приложением запускается master, который делает примитивный round-robin или что-то более сложное, организовывая распределение нагрузки.


                    1. Edison
                      29.08.2018 12:12

                      Один поток будет обрабатывать одновременно только один запрос.


                      Почитайте все таки про многопоточное и конкурентное программирование. Особенно в Go — горутины, каналы и так далее.


                      1. SirEdvin
                        29.08.2018 12:20

                        Возможно я чего-то не понимаю, давайте я попробую объяснить, что я имею ввиду на примере:


                        @app.get('/test')
                        async def test_handler(request):
                            result1 = await first_database_request()
                            result2 = await second_database_request(result1)
                            result3 = await file_reading(result2)
                            return json(result3)

                        Вот этот хендлер будет обрабатывать несколько запросов одновременно. Разумеется, в конкретный момент времени будет выполнятся одна инструкция, но во время того, как будет выполнятся запрос к базе тред не будет заблокирован, а просто будет выполнять другую корутину. И это означает, что несколько запросов будут обрабатываться параллельно, я не прав?


                        1. Edison
                          29.08.2018 12:30

                          И это означает, что несколько запросов будут обрабатываться параллельно, я не прав?

                          Вы не правы, если у вас один тред (как вы сами сказали), выполняться будут конкурентно, не параллельно, просто тред не будет ждать i/o и в это время обрабатывать другой запрос.


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


                          1. SirEdvin
                            29.08.2018 12:47

                            Вы не правы, если у вас один тред (как вы сами сказали), выполняться будут конкурентно, не параллельно, просто тред не будет ждать i/o и в это время обрабатывать другой запрос.

                            Да, вы правы, я использовал не правильный термин, но имел ввиду именно то, что вы описали.


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

                            Я имел ввиду, что конкретно для веб приложения асинхронность + балансировка нагрузки вполне рабочее решение. Это еще один способ реализовать именно параллельную работу, который довольно широко применяется. Я понимаю, что эффективность такого решения скорее всего, значительно хуже в большинстве случаев, но, как я уже говорил выше — эффективность не единственный критерий.


                            Касательно каналов, как часто вы используете их не как очередь и не из-за того, что у вас недостаток контроля над горутинами?


                            1. Edison
                              29.08.2018 13:41

                              Касательно каналов, как часто вы используете их не как очередь и не из-за того, что у вас недостаток контроля над горутинами?

                              Каналы использую для коммуникации (в основном) и управления (старт-стоп).


                              Вы разницу понимаете между каналами в Го внешней очередью?


                              1. SirEdvin
                                29.08.2018 13:50

                                Вы разницу понимаете между каналами в Го внешней очередью?

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


                                1. Edison
                                  29.08.2018 14:01

                                  ну так как вы можете это сравнивать? Так же как сравниваете горутины и процессы питона.

                                  Вы сравниваете многопоточное приложения с практически распределенными системами. Это разные уровни.
                                  Точно так же сервисы на Go будет использовать внешнею очередь для коммуникации между сервисами.


  1. kilgur Автор
    27.08.2018 16:05

    (del) не туда


  1. memba
    27.08.2018 16:20

    $ go mod init mod

    Почему не так: go init
    Инициализировали пакет, создался файл go.package.
    Всё, работаем с пакетом и дальше. Зачем вводить какую-то новую сущность…


    1. kilgur Автор
      27.08.2018 16:43

      М.б. потому что модули — пока фича экспериментальная и с особенностями. И да, go обновился с 1.10 до 1.11 — ломать ничего нельзя, только добавлять.


      1. memba
        27.08.2018 16:58

        Но это же ничего не ломает.


        1. kilgur Автор
          27.08.2018 17:06

          и надо, чтобы не сломалось и потом. пока модули экспериментальные, работа с ними происходит через субкоманды команды mod. если не зайдет, забыли и ладно.
          или я ваш вопрос как-то неправильно понял?


  1. QtRoS
    27.08.2018 20:46

    Правильно я понимаю, что выкачать все зависимости в папку vendor теперь моветон? И чтобы в жёсткой корпоративной среде себя от роском… сбоев гитхаба нужно понимать локальное хранилище а-ля гитлаб?


    1. kilgur Автор
      27.08.2018 21:04
      -1

      Ещё пока не моветон. Отказ от вендоринга — это мнение автора. В официальных доках я такого нигде не встречал. Просто у модулей "ноги растут" из vgo, который "детище" Пайка. Если модули "зайдут" (imho, не если, а когда), vgo забросят, а с ним и остальные инструменты вендоринга станут неправославными. В общем, судя по активности, модули и wasm — это тренд, но пока ещё далеко не мэйнстрим (три дня назад всего релиз был).


      1. Edison
        27.08.2018 23:59
        +1

        Просто у модулей "ноги растут" из vgo, который "детище" Пайка.

        Нет, vgo это прототип от Russ Cox.


        Если модули "зайдут" (imho, не если, а когда), vgo забросят, а с ним и остальные инструменты вендоринга станут неправославными.

        Так модули в Go это и есть vgo — его Расс сделал и предложил включить в Go.


        Не путайте людей.


        1. kilgur Автор
          28.08.2018 08:03

          Нет, vgo это прототип от Russ Cox.

          Точно. Это я попутал. Спасибо за исправление.

          Так модули в Go это и есть vgo — его Расс сделал и предложил включить в Go.

          Вот, тут не соглашусь. Да, разработку перенесли в основной репозиторий. Да, изменения зеркалируют в репо vgo (пока). Но все-таки это разные продукты. Можно в модулях убрать вендоринг, а в vgo оставить. Ну, и так далее… Слияния/поглощения не было, значит, это разные продукты.


          1. 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.


            1. Edison
              28.08.2018 09:12

              я имею ввиду, что коммиты не зеркалируются, vgo — это go mod в отдельном репозитории. Сделано это для go 1.10 пользователей. Не думаю, что будут как-то отдельно развивать vgo, смысла нету две код базы поддерживать.


              1. 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 и модулям как к разным штукам


                1. Edison
                  28.08.2018 14:26

                  Нет, я не буду использовать vgo, у меня уже go 1.11. vgo только для тех, кто хочет использовать модули в go 1.10.


    1. Edison
      27.08.2018 23:55

      нет, типа гитлиба не надо, есть уже github.com/gomods/athens — подымаешь у себя прокси athens, указываешь в GOPROXY и все ок


  1. shuron
    27.08.2018 22:38

    мне, как проведшего большую часть прохграммисткой деятельности с явой, приятно видеть это развитие.
    Понятно и привычно. Если в дув словах, это нужно практически сраку кода ты например не гугл с их monorepo.


  1. istepan
    28.08.2018 07:17

    Все еще недоумеваю почему они сразу так не сделали?!

    Ждем статью про GOPROXY.