Горутины: всё, что вы хотели знать, но боялись спросить

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

    Что за горутины?

    Горутина (goroutine) — это функция, выполняющаяся конкурентно с другими горутинами в том же адресном пространстве.

    Запустить горутину очень просто:
    go normalFunc(args...)

    Функция normalFunc(args...) начнет выполняться асинхронно с вызвавшим ее кодом.

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

    Сколько вешать в граммах?

    Чтобы было проще ориентироваться, рассмотрим цифры полученные опытным путем.

    В среднем можно рассчитывать примерно на 4,5kb на горутину. То есть, например, имея 4Gb оперативной памяти, вы сможете содержать около 800 тысяч работающих горутин. Может этого и недостаточно, чтобы впечатлить любителей Erlang, но мне кажется, что цифра весьма достойная :)

    И все же не стоит бездумно выделять функцию в горутину где только можно. Пользу это принесет в следующих случаях:

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

    «Достаточно велико» — это сколько? Сложный вопрос. Скорее всего решать придется в зависимости от конкретной ситуации. Скажу только, что из моего опыта на моем железе (atom d525 64bit) это ~50мкс. А вообще, тестируйте и развивайте чутье ;)

    Системные потоки

    В исходном коде (src/pkg/runtime/proc.c) приняты такие термины:
    G (Goroutine) — Горутина
    M (Machine) — Машина

    Каждая Машина работает в отдельном потоке и способна выполнять только одну Горутину в момент времени. Планировщик операционной системы, в которой работает программа, переключает Машины. Число работающих Машин ограничено переменной среды GOMAXPROCS или функцией runtime.GOMAXPROCS(n int). По умолчанию оно равно 1. Обычно имеет смысл сделать его равным числу ядер.

    Планировщик Go

    Цель планировщика (scheduler) в том, чтобы распределять готовые к выполнению горутины (G) по свободным машинам (M).



    Рисунок и описание планировщика взяты из работы Sindre Myren RT Capabillites of Google Go.

    Готовые к исполнению горутины выполняются в порядке очереди, то есть FIFO (First In, First Out). Исполнение горутины прерывается только тогда, когда она уже не может выполняться: то есть из-за системного вызова или использования синхронизирующих объектов (операции с каналами, мьютексами и т.п.). Не существует никаких квантов времени на работу горутины, после выполнения которых она бы заново возвращалась в очередь. Чтобы позволить планировщику сделать это, нужно самостоятельно вызвать runtime.Gosched().

    Как только функция вновь готова к выполнению, она снова попадает в очередь.

    Выводы

    Скажу сразу, что меня текущее положение дел сильно удивило. Почему-то подсознательно я ожидал большей «параллельности», наверное потому что воспринимал горутины как легковесные системные потоки. Как видите, это не совсем так.

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

    Так же хочу отметить, что на данный момент планировщик отнюдь не совершенен, о чем упоминается в документации и официальном FAQ. В будущем планируются серьезные улучшения. Лично я в первую очередь ожидаю возможность задания горутинам приоритета.
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 14
    • 0
      В Google App Engine кажется можно только 1 процесс запускать. Получается в GAE goroutines не набалуешься? Или я путаю здесь понятия?
      • +2
        В GAE можно использовать горутины. Все они будут исполняться в одном потоке. То есть представьте себе приведенную в статье схему, но только с одной Машиной. Горутины — это средство достижения многозадачности. Одного потока для этого вполне достаточно.
    • 0
      Спасибо за публикацию! Положил в закладки.
      • 0
        Всегда пожалуйста :)
      • –1
        Go точно нужен?
        Что-нибудь большое на нем уже написали?
        • +2
          Большое не писал, но писал много маленького, работающего 24/7 под большими нагрузками.
          Сервер сбора статистики облака, MySQL-proxy с разбором запросов и unix-сокетами, мониторинг новых и обновление записей в DNS-сервере.
          Работает без падений, uptime с момента последнего обновления версии тулзы.
          • –3
            Erlang для бедных?
            • +2
              Сложно сравнивать разные языки с разными применениями.
        • +2
          Добавлю сюда ссылку на слайды от Rob Pike, где популярно описывается что такое горутины и как они работают.
          concur.rspace.googlecode.com/hg/talk/concur.html
          • 0
            Оффтоп: интересно бурундука этого Пайк сам нарисовал? По сравнению с тем, что изображают на книжках с Питоном — очень дружелюбный зверь :)

            Спасибо за слайды!
            • 0
              у него есть имя — Gopher =)
              • 0
                В честь сетевого протокола или в честь суслика?
                Для суслика он толстоват :)
            • 0
              > Лично я в первую очередь ожидаю возможность задания горутинам приоритета.

              ocyril, не уверен, что это случится: www.youtube.com/watch?feature=player_detailpage&v=f6kdp27TYZs#t=2924s

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

              Самое читаемое