🏃 Горутины: что такое и как работают
Легковесная, потребляет мало памяти, имеет низкую задержку — знакомимся с горутиной.
Язык Go, имеющий встроенную поддержку параллельного программирования, представляет вашему вниманию легковесные потоки, выполняющиеся в фоновом режиме. В отличие от потоков, существующих в большинстве других языков — они дешевле с точки зрения занимаемой памяти, межпотокового взаимодействия, имеют низкую задержку и быстрое время запуска. Хотите детальное описание этой сущности — читайте статью!
Определение потока в программировании
Любое работающее приложение, с технической точки зрения — это процесс или, скорее, последовательное выполнение набора инструкций. Эти инструкции исполняются по порядку: сначала первая, потом вторая, затем следующая и так далее. Этот процесс имеет начало и конец.
Отдельный поток подобен процессу и также имеет начало, определенную последовательность действий и конец. Но, в отличие от процесса, поток не является программой и не может работать сам по себе. Вместо этого, он выбирает часть из этой последовательности действий программы и выполняют ее как отдельное приложение. Реальное преимущество этой концепции заключается в том, что потоков может быть несколько, и каждый может одновременно с другими выполнять свою задачу в одной программе.
Простым примером многопоточности является веб-браузер. Ведь в нем можно одновременно загружать файлы, прокручивать страницы вниз, что-то печатать и слушать музыку. Технически, поток можно назвать облегченным процессом, ведь он требует меньше памяти, чем процесс в многопроцессорной среде. Linux обычно обобщает их, называя задачами (они могут быть как процессами, так и потоками).
Однако, между ними все же есть различие.
Что такое Горутины?
Горутины — это дальнейшее усовершенствование концепции потока, а если сказать проще, то это функции, способные работать параллельно с другими такими же функциями в одном адресном пространстве. Причем их настолько хорошо усовершенствовали, что они стали отдельной сущностью. В многопроцессорной среде создание и обслуживание процесса сильно зависят от базовой операционной системы. Процессы потребляют ресурсы операционки и не делят их между узлами. Потоки, хотя и легче, чем процессы, из-за совместного использования ресурсов (между одноранговыми потоками), требуют большого размера стека — почти 1 МБ. Причем стек нужно умножать на количество потоков.
Кроме того, их переключение требует восстановления регистров, таких как программные счетчики, указатели стека, регистры с плавающей запятой и т. д. Из-за этого стоимость обслуживания процесса или потока довольно высока. Кроме того, в случаях, когда данные совместно используются одноранговыми узлами, возникают дополнительные затраты на синхронизацию данных. Хотя накладные расходы на переключение между задачами максимально оптимизированы, постановка новых задач по-прежнему требует больше ресурсов. Иногда это сильно снижает производительность приложения, даже если потоки обозначены как легковесные.
Преимущество горутин в том, что они не зависят от базовой операционной системы, а скорее, существуют в виртуальном пространстве среды выполнения Go. В результате любая оптимизация горутины меньше зависит от платформы, на которой она работает. Они начинают работать с начальной емкости стека размером всего 2-4 КБ и наряду с каналами поддерживают модели параллелизма взаимодействующих последовательных процессов (CSP), где значения передаются между независимыми действиями. Эти действия, как вы уже догадались, называются горутинами.
Как создать горутину в Go
Разработчики должны понимать, что горутины превосходят потоки только количественно. Качественно они одинаковы. При запуске программы в Golang первая горутина вызывает основную функцию и из-за этого ее часто называют основной горутиной. Если же мы захотим создать другие, новые горутины, мы должны использовать оператор go. Например, чтобы вызвать функцию в Golang мы пишем так:
Здесь, после того как ее вызвали, мы опять вернемся к точке вызова. А теперь мы напишем:
Префикс go
вызывает функцию в новой горутине и она (функция) будет выполняться асинхронно с вызвавшим ее участком кода. И если примерно брать в среднем на одну горутину по 4 Кб емкости стека, то имея оперативную память 4Gb, мы сможем создать их около 800 000.
Однако злоупотреблять с ними не стоит, ведь полезны они будут только в следующих случаях:
- Когда нам необходима асинхронность. Например, при работе с сетью, дисками, базами данных и т. д.
- При большом времени выполнения функции, когда мы можем что-то выиграть, нагрузив другие ядра.
Среда выполнения Go, работающая в фоновом режиме, начинает запуск набора горутин, с помощью планировщика, распределяющего их по машинам. Затем он создает поток, обрабатывающий все горутины, а максимум определяется переменной GOMAXPROCS
.
GOMAXPROCS
ограничивает количество потоков операционной системы, которые могут одновременно выполнять код Go на уровне пользователя. Количество потоков, которые можно заблокировать в системных вызовах от имени кода Go, не ограничено.Давайте рассмотрим простой пример для лучшего понимания работы горутин:
Запуск приведенного выше кода в вашем редакторе приведет к следующему результату:
Ниже приведен тот же код, только без использования горутин:
Здесь ожидаемый результат будет следующим:
Когда мы ставим перед любой функцией ключевое слово go
, оно создает новую горутину и планирует ее выполнение. Идея и поведение такой сущности полностью идентичны потокам: она имеет полный доступ к аргументам, глобальным переменным и остальным доступным элементам кода. Кроме того, мы можем писать горутины с анонимными функциями. Если вы удалите вызов sleep()
в первом примере, вывод не будет показан. Следовательно, вызов функции в конце main
приведет к тому, что основная горутина остановится и выйдет до того, как порожденная ею горутина получит какой-либо шанс произвести вывод.
Вывод второй части нашего примера довольно прост и последователен, чего нельзя сказать о выводе первой части. Это связано с тем, что компилятор ограничивает порядок доступа к памяти в случае одновременного выполнения горутин и в таком случае невозможно предсказать порядок выводимых строк.
Если подытожить, то можно сказать, что горутины — это эффективное ресурсосберегающее средство достижения многозадачности в языке программирования Go. Более подробную информацию по их грамотному использованию можно получить на официальном сайте языка.