Код
#статьи

Язык Go: что под капотом и зачем программисту учить его как второй

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

Иллюстрация: Катя Павловская для Skillbox Media

Однажды в Google решили создать удобную и мощную альтернативу C++. Так появился Golang, который стабильно занимает высокие позиции в рейтингах языков программирования и привлекает новых разработчиков.

Язык создали Роб Пайк и Кен Томпсон. Оба — культовые личности в computer science и в прошлом сотрудники легендарной Bell Labs. А Томпсон к тому же один из создателей ОС UNIX и языка B (предшественника C).

Из этой статьи вы узнаете:


Что такое язык программирования Go

Go, или Golang, — это компилируемый многопоточный язык с открытым исходным кодом. В основном его применяют в веб-сервисах и клиент-серверных приложениях. В конце 2021 года Golang даже вошёл в топ-5 востребованных языков и опередил PHP, C# и TypeScript.

Авторы языка попытались объединить лёгкость разработки на Python и скорость исполнения программ на C и C++, поэтому сделали Go компилируемым. И хотя в экосистеме Go есть свой интерпретатор, он редко бывает нужен. Код и так шустро компилируется.

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

Особенности языка Go

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

Основными фишками Go стали:

  • Автоматическое управление памятью и сборщик мусора. Go — быстрый, как C/C++, но программировать на нём легче. Если в C/ C++ приходится вручную управлять памятью, то компилятор Golang берёт эти заботы на себя.
  • Синтаксический сахар. Это синтаксические послабления, которые позволяют писать код быстрее. Например, формально некоторые операции в Go (if, for) должны заканчиваться точкой с запятой, но на деле компилятор сам способен расставить точки с запятыми в нужных местах.
  • Автоматическое форматирование программ. Golang сам расставляет отступы и выравнивает элементы по колонкам с помощью команды gofmt. Но важно использовать только табуляцию для отбивки строк — пробелы в начале строки gofmt не поймёт.
  • Автоматическое создание документации. Команда godoc найдёт все комментарии и сделает из них мануал к программе.
  • Отслеживание устаревших конструкций. Инструмент gofix сканирует код и отмечает синтаксические конструкции, которые считаются устаревшими по современным стандартам.
  • Инструменты тестирования. В Go включено множество инструментов тестирования. Например, typecheck проверяет соответствие типов в коде, golint даёт рекомендации на основе официальной документации — Effective Go и CodeReviewComments, gosimple упрощает сложные синтаксические конструкции, а gas находит уязвимости в коде.
  • Отслеживание состояния гонки. Для работы с многопоточными системами очень важно выполнять функции в правильном порядке, чтобы не перепутать данные, потому что состояние гонки — очень коварная ошибка. Она может возникать случайным образом, из-за этого локализовать её почти невозможно. Golang изначально спроектирован так, чтобы свести такие ошибки к минимуму. А если что-то и проскочит — есть дополнительные инструменты для проверки кода на состояние гонки. Чтобы включить детектор гонки, надо добавить флаг —race — на этапе компилирования, сборки, тестирования или установки пакета.
  • Профилирование. В языке программирования Go есть пакет pprof и консольная утилита go tool pprof. Профайлер pprof исследует, какие фрагменты кода выполняются слишком долго, где программа ест много памяти или чересчур нагружает процессор. Результат его работы — текстовый отчёт, профайл. Чтобы визуализировать профайл и построить из него схему, надо установить утилиту graphviz.
Результат работы утилиты graphviz
Скриншот: Go / Skillbox Media
  • Низкоуровневое программирование. Безусловно, язык Go не смог бы претендовать на лавры C и C++, если бы не умел непосредственно работать с памятью. Для этого в нём есть пакет unsafe.

Технические возможности Go

Одно из важных свойств Go — многопоточность. Тут придётся немного погрузиться в историю компьютерных технологий.

В 1965 году Гордон Мур, основатель Intel, сформулировал закон: каждые два года количество транзисторов на интегральной схеме будет удваиваться. Никаких научных данных или формул за ним нет — просто наблюдение. И до XXI века закон Мура работал исправно. Но примерно после Pentium 4 стало понятно: ещё немного, и процессоры будут нагреваться, как сверхновая. Тогда производители начали делать многоядерные процессоры — тактовая частота и количество транзисторов почти не менялись, а суммарное быстродействие росло.

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

Горутины

Это функции, которые могут работать параллельно, то есть программа выполняет несколько строк практически одновременно. Чтобы сделать из функции горутину, надо просто написать перед ней go.

Вот как это выглядит:

func server(i int) {
	for {
		print(i)
		time.Sleep(10)
	}
}
go server(1)
go server(2)

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

За выполнением горутин в Go следит специальная библиотека времени исполнения: она распределяет между ними ядра процессора, может ограничивать число доступных ядер. Библиотека помогает запускать огромное количество горутин — намного больше, чем позволяет операционная система, — и не требует от программиста заниматься распараллеливанием вручную.

Каналы

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

package main

import "fmt"

func main() {
    channel := make(chan float32)

    fmt.Printf("type of 'c' is %T\n", channel)
    fmt.Printf("value of 'c' is %v\n", channel)
}
Запустить на play.golang.org

Немного похоже на работу с переменными — используем оператор присваивания и сразу задаём тип данных. Но интересно, что значением канала будет его адрес в памяти (вывод второго оператора Printf).

Теперь объединим горутины и канал:

package main

import "fmt"

func gorutine_test(channel chan string) {
    fmt.Println("Hey, " + <-channel + "!")
}

func main() {
    fmt.Println("main() started")
    channel := make(chan string)

    go gorutine_test(channel)

    channel <- "Rob"
    fmt.Println("main() stopped")
}
Запустить на play.golang.org

А сейчас следите за руками — будем разбирать код:

  • Объявляем функцию gorutine_test с аргументом channel. Результат её работы — строка приветствия и данные из канала, мы считываем их с помощью оператора <-.
  • Функция main первым делом выводит на экран сообщение о том, что она стартовала.
  • После этого мы создаём канал channel и задаём ему тип данных string.
  • Теперь запускаем функцию gorutine_test как горутину и помещаем в неё канал channel.
  • Сейчас и main, и gorutine_test активны.
  • Теперь мы помещаем в канал имя создателя языка программирования Go — Rob. Функция main тут же блокируется, пока gorutine_test не считает данные из канала. Заметьте, gorutine_test вызывается раньше, чем мы отправляем значение в канал, но планировщик Go выполняет именно её.
  • После этого функция main разблокируется и выводит сообщение о том, что она закончила работу.

    В многопоточность отлично вписывается функциональная парадигма программирования, и язык Go во многом поддерживает её. В нём, конечно, присутствуют императивные конструкции, элементы ООП и всё такое. Но именно ярко выраженная функциональная парадигма делает Golang мощным инструментом для высоконагруженных серверных решений, сервисов и сложных вычислений.

Типы данных в Go

Go — язык со строгой статической типизацией, то есть каждая переменная имеет свой тип и менять его нельзя. Сравним с PHP:

<?php
$foo = "1";  // $foo — это строка (ASCII-код 49)
$foo *= 2;   // $foo теперь целое число (2)
$foo = $foo * 1.3;  // $foo теперь число с плавающей точкой (2.6)
$foo = 5 * "10 Little Piggies"; // $foo — это целое число (50)
$foo = 5 * "10 Small Pigs";     // $foo — это целое число (50)

$a = "1.5"; // $a — это строка
$b = 100; // $b — это целое число
$c = $a * $b; // $c — это число с плавающей точкой, значение — 150.0
?>

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

В языке программирования есть 11 типов целых чисел. Они различаются количеством бит, спецификой (например, есть отдельный тип byte для двоичных чисел) и контекстом (например, uintptr для работы с внешним кодом). Кроме того, есть числа с плавающей точкой, комплексные числа, булевы числа, строки и три типа чисел с неограниченной точностью, которые могут принимать любое значение и ограничены только объёмом памяти компьютера.

Переменные в Go объявляются в стиле Pascal — через оператор var, а само объявление можно совмещать с присваиванием:

var v1 int // Объявляет переменную v1 и задаёт ей тип «целое число»
var v2 string = "teach Go, friend" // Объявляет переменную v2, задаёт ей тип «строка» и присваивает значение «teach Go, friend»
v1 := v2 // То же, что и var v1 = v2, объявляет переменную и задаёт ей значение
  • В первой строке мы объявляем переменную v1 и задаём ей тип «целое число»;
  • во второй строке — объявляем переменную v2, задаём ей тип «строка» и присваиваем значение teach Go, friend;
  • в третьей строке — делаем то же, что и v1 = v2, объявляем переменную и задаём ей значение.

Оператор присваивания в Go — знак =:

a = b
i, j = j, i

Здесь мы в первой строке присвоили переменной a значение b, а вот во второй — поменяли местами значения i и j.

Как установить и начать использовать язык программирования Go

Скачать Go для разных платформ можно на официальном сайте: есть готовые сборки для Windows, macOS, Linux. Также исходники можно скомпилировать на куче операционок — FreeBSD, OpenBSD, DragonFly BSD, Solaris, Android, AIX, Plan 9 (кстати, тоже детище Томпсона и Пайка с названием — отсылкой к фильму Эда Вуда, самого знаменитого неудачника фабрики грёз).

Чтобы проверить, успешно ли установился Go на Windows, введите команду go в командной строке.

Скриншот: Skillbox Media

Писать код на Go можно в программах трёх типов — кому что больше подходит:

  • Текстовый редактор с подсветкой синтаксиса Go, автодополнением, компиляцией и отладкой. Обычно реализованы плагинами — например, в Notepad++, Vim, Emacs.
  • Универсальная среда разработки (IDE): Eclipse, NetBeans, IntelliJ IDEA, Komodo, Codebox, Visual Studio, Zeus IDE и другие.
  • Специализированная среда разработки для Golang. Самые известные — коммерческая GoLand от JetBrains и опенсорсная LiteIDE.

Первая программа на Go

По традиции это, конечно, Hello, World!. Ниже — разбор синтаксиса:

package main

import "fmt"

func main() { 
	fmt.Println("Hello, World!")
}
  • package main — даём имя пакету, так надо для файлов, которые будут исполняться;
  • import 'fmt' — вызываем пакет, который отвечает за форматирование и вывод информации (такие пакеты ещё называются библиотеками);
  • func main() — каждый исполняемый файл должен включать главную функцию — main;
  • fmt.Println («Hello, World!») — вызываем функцию Println из пакета fmt, она выводит информацию из круглых скобок на экран;
  • «Hello, World!» — кавычки показывают, что надо вывести как строку всё, что внутри;
  • // — а вот так обозначаются однострочные комментарии, всё, что идёт после этого символа и до конца строки, компилятор Go пропускает.

Теперь давайте сделаем что-то посложнее. Напишем функцию, которая будет возвращать числа Фибоначчи:

package main

import "fmt"

func fib() func() int {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}

func main() {
	f := fib()
	fmt.Println(f(), f(), f(), f(), f())
}
  • Первые две строки точно такие же, как и в прошлом примере.
  • func fib() func() int — объявляем функцию, которая будет возвращать очередное число Фибоначчи. Она называется fib и возвращает другую функцию — func() int. Это нужно, чтобы мы при каждом новом вызове получали следующее число Фибоначчи.
  • a, b: = 0, 1 — создаём две переменные и присваиваем им значения 0 и 1 соответственно. Это нужно, чтобы вычислить следующее число Фибоначчи.
  • return func() int — возвращаем функцию.
  • a, b = b, a+b — считаем следующее число Фибоначчи.
  • return a — возвращаем очередное число Фибоначчи.
  • func main — вызываем главную функцию main.
  • f: = fib() — создаём переменную f и кладём в неё функцию, которая при каждом вызове будет возвращать следующее число Фибоначчи.
  • fmt.Println(f(), f(), f(), f(), f()) — выводим первые пять чисел Фибоначчи.

Что в итоге

Go — мощный, изящный и современный язык программирования, по скорости сравнимый с C и C++, а по простоте создания кода — с Python. Освоить его может даже новичок. У Golang простая, лаконичная документация и дружелюбное сообщество, где всегда можно задать вопрос, — опытный программист быстро выучит его как второй язык. Перспективы вполне серьёзные, на долгое время: язык поддерживается Google, но живёт как самостоятельный свободный проект с открытыми исходниками.

Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.

Участвовать
Школа дронов для всех
Учим программировать беспилотники и управлять ими.
Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована