Что такое горутины golang

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

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

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

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

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

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

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

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

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

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

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

50мкс. А вообще, тестируйте и развивайте чутье 😉

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

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

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

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

Что такое горутины golang. Смотреть фото Что такое горутины golang. Смотреть картинку Что такое горутины golang. Картинка про Что такое горутины golang. Фото Что такое горутины golang

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

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

Выводы

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

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

Источник

Параллельное программирование. Горутины

Горутины

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

Каждая горутина, как правило, представляет вызов функции, и последовательно выполняет все свои инструкции. Когда мы запускаем программу на Go, мы уже работаем как минимум с одной горутиной, которая представлена функцией main. Эта функция последовательно выполняет все инструкции, которые определены внутри нее.

Например, определим несколько горутин, вычисляющих факториал числа:

Однако вместо шести факториалов на консоли при вызове программы мы можем увидеть только строку «The End»:

«Можем увидеть» означает, что поведение программы в данном случае недетерминировано. Например, вывод может быть и таким:

Почему так происходит? После вызова go factorial(i) функция main запускает горутину, которая начинает выполняться в своем контексте, независимо от функции main. То есть фактически main и factorial выполняются параллельно. Однако главной горутиной является вызов функции main. И если завершается выполнение этой функции, то завершается и выполнение всей программы. Поскольку вызовы функции factorial представляют горутины, то функция main не ждет их завершения и после их запуска продолжает свое выполнение. Какие-то горутины могут завершиться раньше функции main, и соответственно мы сможем увидеть на консоли их результат. Но может сложиться ситуация, что функция main выполнится раньше вызовов функции factorial. Поэтому мы можем не увидеть на консоли факториалы чисел.

Теперь мы сможем увидеть результаты всех вызовов функции factorial:

Горутины также могут представлять вызовы анонимных функций:

Источник

Краткое введение в горутины и каналы

Что такое горутины golang. Смотреть фото Что такое горутины golang. Смотреть картинку Что такое горутины golang. Картинка про Что такое горутины golang. Фото Что такое горутины golang

Что такое горутина?

Как реализовать горутину

Теперь давайте посмотрим на код. Допустим, я не хочу получать доступ к стороннему API для получения информации. Мне нужно довольно много информации, и она нужна мне быстро. Зная, что большая часть времени, необходимого для получения данных из простого HTTP-запроса, тратится на ожидание ответа удаленного сервера, я решаю использовать параллелизм.

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

Наконец, мы присваиваем переменной тела значение, возвращаемое в результате вызова функции ioutil.ReadAll, которая принимает параметр, реализующий интерфейс io.Reader (который довольно часто реализуется в Go), и возвращает массив байтов, который мы впоследствии можно легко преобразовать в строку.

Теперь давайте вызовем эту функцию в нашей основной функции. Я буду использовать свой любимый API, Rick and Morty API, который я использую во всех своих примерах (к счастью, не очень многие люди реализуют код из моих статей, иначе этот сервер API будет недоступен 24/7):

Чтобы я мог объяснить, позвольте мне показать вам версию, которую вы ожидали, и то, как мы будем вызывать ее в основной функции (спойлер: это не будет работать должным образом):

Причина, по которой это ничего не возвращает, заключается в том, что основная функция, по сути, является собственной горутиной, она просто прошла цикл for и вышла. Его не волнует, что остальные 200 горутин еще не закончили свою работу.

Хорошо, но как же тогда основная функция получает доступ к этим значениям, спросите вы? Посмотрим в следующем фрагменте кода:

Кстати, этот код включает в себя все объявления пакетов и импорта для людей, которые здесь только для копирования / вставки.

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

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

Наконец, мы печатаем все на консоль, и, поскольку она выдает 200 результатов, ваша консоль, вероятно, взорвется, как моя:

Источник

Что такое горутины и каков их размер?

А сейчас традиционно публикуем полезный перевод.

Я почти уверен, что любой, кто изучал когда-либо Go, слышал, что «горутины похожи на легковесные потоки» и что «можно спокойно запускать сотни и тысячи горутин». Некоторые люди узнавали, что «горутина весит около 2 килобайт», скорее всего, из примечаний к релизу Go 1.4, и еще меньше людей узнавали, что это изначальный размер ее стека.

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

В рамках этого исследования я буду использовать ветку Go 1.14, поэтому все фрагменты кода будут ссылаться на нее.

Планировщик горутин

Планировщик горутин (Goroutine scheduler) является перехватывающим задачи (work-stealing) планировщиком, который был введен еще в Go 1.1 Дмитрием Вьюковым вместе с командой Go. Его диздок доступен здесь и включает рассуждения на тему возможных будущих улучшений. Существует множество замечательных ресурсов, помогающих разобраться, как он работает, но основная суть заключается в том, что он пытается управлять G, M и P; горутинами, машинами (потоками) и процессорами.

«P» можно рассматривать как ЦП в планировщике ОС; он представляет ресурсы, необходимые для выполнения нашего Go кода, такие как планировщик или состояние распределителя памяти.

В рантайме они представлены как структуры: type g, type m или type p.

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

Когда M прекращает выполнение нашего кода, он возвращает свой P в пул свободных P. Чтобы возобновить выполнение Go кода, он должен повторно заполучить его. Точно так же, когда горутина завершается, объект G возвращается в пул свободных G и позже может быть повторно использован для какой-либо другой горутины.

Чтобы запустить горутину, запускающую на выполнение main либо из кода, структура g инициализируется с помощью функции malg

Итак, теперь мы готовы проанализировать саму горутину!

Объект горутины

Длина объекта горутины составляет порядка 70 строк. Позвольте мне удалить комментарии и навести небольшой порядок:

Есть более сложные типы, такие как timer (

40 байт) или _defer (

100 байт), но в целом я насчитал около 600 байт.

Хм, это кажется немного подозрительным… Откуда взялось пресловутое значение «2 кб»?

Давайте подробнее рассмотрим первое поле структуры…

Стек горутины

Сам стек представляет собой не что иное, как два значения, обозначающих его начало и конец.

К этому времени вы, вероятно, зададитесь вопросом: «А каков же размер этого стека?», или уже догадаетесь, что 2 килобайта относятся к этому стеку!

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

Эта отличная статья Дэйва Чейни более подробно объясняет, как это работает. По сути, перед выполнением любой функции Go проверяет, доступен ли объем стека, необходимый для функции, которую он собирается выполнить; если нет, то выполняется вызов runtime.morestack, который выделяет новую страницу, и только после этого выполняется функция. Наконец, когда эта функция завершается, ее возвращаемые аргументы копируются обратно в исходный фрейм стека, а все невостребованное пространство стека высвобождается.

Хотя минимальный размер стека определен как 2048 байтов, рантайм Go также не позволяет горутинам превышать максимальный размер стека; этот максимум зависит от архитектуры и составляет 1 ГБ для 64-разрядных систем и 250 МБ для 32-разрядных систем.

Если этот предел достигнут, будет выполнен вызов runtime.abort. Превысить этого размер очень просто с помощью рекурсивной функции; все, что вам нужно сделать, это

Итак, сколько горутин вы можете запустить?

Я использую скрипт из Приложения к статье, скопированный отсюда.

На ноутбуке среднего уровня я могу запустить 50 миллионов горутин.

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

Заключение

На этом пожалуй все!

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

Я рекомендую бегло просмотреть src/runtime/HACKING.md, где многие концепции и соглашения кода в среде выполнения Golang объясняются более подробно.

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

Источник

Изучаем многопоточное программирование в Go по картинкам

Однопоточные и многопоточные программы

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

Именно таким будет наш первый пример — программа добывающая руду. Наши функции будут искать, добывать и перерабатывать руду. Руда в шахте в нашем примере представлена списками строк, функции принимают их в качестве параметров и возвращают список «обработанных» строк. Для однопоточной программы наше приложение будет спроектировано следующим образом:

Что такое горутины golang. Смотреть фото Что такое горутины golang. Смотреть картинку Что такое горутины golang. Картинка про Что такое горутины golang. Фото Что такое горутины golang

В этом примере всю работу выполняет один поток(гофер Гари). Три основные функции: поиск, добыча и обработка выполняются последовательно друг за другом.

Если напечатать результат работы каждой функции, мы получим следующее:

Простой дизайн и реализация является плюсом однопоточного подхода. Но что делать, если вы хотите запускать и выполнять функции независимо друг от друга? Тут вам на помощь приходит многопоточное программирование.

Что такое горутины golang. Смотреть фото Что такое горутины golang. Смотреть картинку Что такое горутины golang. Картинка про Что такое горутины golang. Фото Что такое горутины golang
Такой подход к добыче руды гораздо эффективнее. Теперь несколько потоков(гоферов) работают независимо, и Гари делает только часть работы. Один гофер ищет руду, другой добывает, а третий переплавляет, и все это потенциально одновременно. Для того чтобы реализовать такой подход, в коде нам нужны две вещи: создавать гоферов-обработчиков независимо друг от друга и передавать между ними руду. В Go для этого существуют горутины и каналы.

Горутины

Горутины можно представлять себе как «легковесные потоки», чтобы создать горутину нужно просто поставить ключевое слово go пред кодом вызова функции. Чтобы продемонстрировать насколько это просто, давайте создадим две функции поиска, вызовем их с ключевым слово go и будем печатать сообщение каждый раз когда они найдут «руду» в своем руднике.
Что такое горутины golang. Смотреть фото Что такое горутины golang. Смотреть картинку Что такое горутины golang. Картинка про Что такое горутины golang. Фото Что такое горутины golang

Вывод нашей программы будет таким:

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *