Андроид. Windows. Антивирусы. Гаджеты. Железо. Игры. Интернет. Операционные системы. Программы.

Язык программирования go для чайников. Почему язык Go? Важные преимущества. Основы языка Go

Go - это язык программирования, который был разработан корпорацией Google. Часто его еще называют “Golang” - сокращение от “Google language”. Это компилируемый и многопоточный язык, релиз которого состоялся в ноябре 2009 года, а самый свежий выпуск 1.6 датируется февралем 2016 года. Одной из ключевых фигур, которые принимали участие в создании Go, является Роб Пайк, известный разработчик языков программирования, а также операционных систем, в данный момент работающий в Google. Выступая на одной из конференций, он отметил, что язык Go - это попытка перенять лучшие стороны таких языков, как C++ и Java.

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

Применение

Язык Go разрабатывался как язык для создания различных высокоэффективных программ, однако большинство программистов сходятся во мнении, что лучше всего он подходит для создания веб-приложений (в качестве back-end). При этом Go дает возможности писать и другие проекты, к примеру, Docker, InfluxDB и Kubernetes. По сути, применение языка Go ограничивается тремя основными направлениями: сетевое программное обеспечение, консольные утилиты и бэкенд.

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

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

Плюсы языка Go

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

Во-первых, это его простота. Язык Go разрабатывался в качестве замены C: его высокая производительность почти сопоставима с языком Си, но более простой синтаксис дает возможность разрабатывать приложения гораздо быстрее (к примеру, как на Python). При этом многие разработчики изучают этот язык после Python или PHP либо используют два языка в связке (Python/Go и PHP/Go). Упрощенный синтаксис облегчает не только написание своего собственного кода, но и чтение кода, написанного другими программистами, что особенно важно в командной работе. Эта особенность Go, в свою очередь, ведет к другому важному факту: быстрое освоение Go позволяет перевести внимание с изучения самого языка на изучение программирования в целом.

Программисты постоянно сравнивают язык Go с другими языками: в его пользу свидетельствует тот факт, что, к примеру, в PHP существует 67 ключевых слов, а в Go их всего лишь 25.

Тут необходимо сделать отступление и сказать о том, что желание создать простой язык программирования родилось у разработчиков Google неслучайно: изначально они задумывали Go как быстро изучаемый язык, который запросто могут выучить несколько не очень опытных программистов для того, чтобы затем вместе писать какое-нибудь приложение. Именно поэтому некоторые считают язык Go языком, пригодным для больших корпораций, где в разработке продуктов будет участвовать множество разных людей. Как уже было замечено выше, простой и понятный синтаксис ведет к тому, что программистам Go не нужно вводить какие-то другие стандарты, обозначения или комментарии - они и без этого отлично поймут код друг друга. Многословность Go радует одних разработчиков и огорчает других, но именно благодаря ей код Go читается так легко.

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

Во-вторых, Go имеет большое количество библиотек, благодаря которым можно выполнить практически любую поставленную задачу. Многое можно реализовать, используя только стандартную библиотеку Go, которая легко читается, а также отличается хорошим качеством и скоростью. Отдельного внимания заслуживает эффективное взаимодействие с библиотеками языка Си; некоторые отмечают, что Go-библиотеки по факту являются просто обертками над С-библиотеками.

В-третьих, Go это компилируемый язык, на котором можно быстро написать необходимое приложение. Go имеет такую же строгую статическую типизацию, как Pascal, но в то же время он имеет гораздо больший прикладной характер (и это роднит его с Python). Тут необходимо отметить высокую производительность Golang, которая характерна для компилируемых языков. Также в Go значительно облегчена кроссплатформенность.

Нельзя не отметить и четкую направленность языка Go: в отличие от PHP, на котором пишут большие проекты, Go больше предназначен для некрупных сервисов, которые необходимо быстро написать и внедрить, но которые должны отличаться чрезвычайной надежностью (так называемое robust software).

Развертка Go-приложения также выглядит намного проще, чем развертка приложения, написанного на PHP. Программируя на Go, вы можете создать артефакты для всех операционных систем или архитектур процессора, вам не нужно будет устанавливать Go на используемые вами сервера.

Синтаксис Go может быть непривычен тем, кто до этого программировал на PHP или Python. Если рассматривать код Go в сравнении с PHP, то Go не имеет объектов или классов, зато в нем есть структура, а точнее тип, который содержит именованные поля:

Type Circle struct { x, y, r float64 }

Еще одной причиной, по которой Go завоевывает все большую популярность, является наличие go fmt - инструмента, который помогает стандартизировать код. А использование go vet поможет вам найти возможные проблемы в коде. Иными словами, авторы языка Go попытались максимально упросить задачу разработки приложений на этом языке, особенно для новичков.

Именно поэтому многие на данный момент считают Go одним из лучших языков программирования и советуют изучать его не только студентам, но и школьникам. Кстати, такой прецедент уже был: учитель научил несколько 11-летних школьников писать и разбираться в коде на таком уровне, что они смогли написать генератор фракталов Мандельброта. А главное, для этого ему понадобилось всего 12-13 часов!

Минусы языка Go

В качестве негативной стороны Go часто называют его малую на данный момент распространенность - действительно, есть куда более распространенные языки программирования, которые используются для выполнения большинства задач. Значит ли это, что изучать Go не стоит? Совсем нет. Существует множество корпораций, которые используют язык Go в своих разработках. В первую очередь это, конечно же, Google Inc., а также Basecamp, BBC Worldwide, Canonical, DigitalOcean, Dropbox, eBay, Yahoo и многие другие компании по всему миру. Поэтому перспективы у этого языка программирования точно есть. Безусловно, программисты Go могут быть менее востребованы, чем, к примеру, PHP-разработчики, однако и конкуренция ниже, а значит, выше шанс найти интересную и хорошо оплачиваемую работу.

Те, кто пытается предугадать перспективы языка Go, также говорят о другом минусе, который фактически является противоположностью предыдущего пункта: из-за легкости языка, а значит, низкого порога вхождения количество людей, программирующих на Go, будет расти в геометрической прогрессии, при этом качество кода и сами проекты будут оставлять желать лучшего. Похожая ситуация в начале XXI века случилась с PHP, когда на этом языке стало писать множество начинающих программистов, не очень волнующихся за работоспособность своего кода (и готовых получать небольшую плату за свой труд). Отличие PHP от Go на данный момент заключается в том, что PHP в итоге стал полноценным языком с богатой инфраструктурой и множество фреймворков, без знания которых вы едва ли найдете хорошую работу. Go же позиционируется как простой язык, поэтому перспективы его развития не очень определены.

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

Вывод

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

Мы привыкли думать, что по-настоящему универсальных языков программирования не существует. Когда нам нужна эффективность - мы пишем на Си и миримся с его ограничениями. Когда нужна скорость разработки - кодим на Python и ожидаем получить медленный код. Erlang позволяет создавать высокораспараллеленные распределенные приложения, но его очень трудно вписать в существующие проекты. Язык Go полностью ломает такую систему мышления, сочетая в себе преимущества многих языков и освобождая программистов от их недостатков.

Когда десять лет назад Кена Томпсона, принимавшего активное участие в разработке языка Си, спросили, каким бы он сделал этот язык на тот момент, он ответил, что язык был бы похож на Limbo. Прошло немало времени, и Томпсон совместно с еще одним автором языка Си, Робом Пайком, принял участие в создании Go - языка, который стал переосмыслением и последующим развитием Limbo. Go был представлен миру 10 ноября 2009 года и практически сразу стал бестселлером. Одни только имена авторов, известных как создатели операционной системы UNIX, языка программирования Си и кодировки UTF-8, а также покровительство Google, в лабораториях которых был создан язык, дали Go отличный старт. Однако даже это не позволило бы языку долго продержаться на плаву, если бы он не смог предложить программистам что-то действительно новое - что-то, что упростило бы их жизнь и сделало Go по-настоящему незаменимым. И это "что-то" в языке было. В большом количестве.

Си сегодняшнего дня

Создатели Go позиционируют свое детище как системный язык, сочетающий в себе эффективность и скорость исполнения кода, написанного на Си, с простотой разработки на более высокоуровневых скриптовых языках, да еще и со встроенными средствами параллельного программирования. При этом внешне Go напоминает какую-то странную солянку из синтаксисов языков Си, Pascal и ADA, что вкупе с приведенным описанием создает довольно сильное ощущение подвоха, почти такое же, какое возникает, когда слышишь о новой мега-разработке пятигорских студентов. Однако оно быстро убывает, когда ты начинаешь изучать язык, и совсем улетучивается, когда узнаешь о том, почему Go стал именно таким, какой он есть.

В основу Go положено три фундаментальных идеи:

  1. Гарантия высокой скорости компиляции и производительности приложений.
  2. Простота разработки и поддержки приложений, свойственная высокоуровневым скриптовым языкам.
  3. Встроенные средства параллельного программирования, позволяющие задействовать все имеющиеся ядра современных процессоров.

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

Производительность

Даже очень простая референсная реализация компилятора с языка Go способна за какие-то доли секунды сгенерировать на удивление быстрый код, скорость исполнения которого будет сопоставима со скоростью исполнения кода, написанного на таких языках, как Си и C++. При этом, в отличие от своих предков, компилятор Go гарантирует проверку типов, а результирующий код получает встроенный сборщик мусора и собственный механизм распараллеливания.

С самого начала язык проектировался таким образом, чтобы быть легко понятным и простым в "переваривании" не только человеку, но и машине. Многие синтаксические и архитектурные элементы Go были задуманы если и не с главной целью, то, по крайней мере, с оглядкой на возможность их простого разбора программой, будь то компилятор, дебаггер или даже среда разработки. Язык получился очень прямолинейным и недопускающим неочевидностей и спорных мест, которые могли бы привести компилятор в замешательство (язык C++ - яркий пример такого неочевидного синтаксиса и общей механики, которые заставляют головы программистов трещать, а компилятор - медленно буксовать на месте).

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

В стандартную поставку Go входят плагины для всех популярных сред программирования, в том числе Vim

Простота разработки и сопровождения

Go - системный язык, что, тем не менее, не мешает ему быть достаточно высокоуровневым для того, чтобы обеспечить программиста всем необходимым для комфортного и быстрого написания кода. Язык включает в себя такие высокоуровневые конструкции, как ассоциативные массивы и строки (которые можно сравнивать, копировать, вычислять длину, делать срезы). Он имеет средства для создания собственных типов данных (подобных классам в других языках), средства создания потоков и обмена данными между ними, и, конечно же, он лишен указателей, способных ссылаться на любой участок памяти (срыв стека в программе, написанной на Go, невозможен в принципе). Однако главное, что дает Go программисту, это та самая прямолинейность и очевидность синтаксиса, о которой мы говорили в предыдущем разделе. В этом смысле Go очень похож на языки Pascal, Modula и Oberon: практически любой синтаксический элемент языка следует общей логике и может быть явно и безошибочно интерпретирован вне зависимости от его положения в коде. Например, совершить знаменитую ошибку объявления переменных, описанную во всех гайдах по стилистике оформления кода на языке Си, в Go просто невозможно:

int* a, b; // В Си и C++ переменная "a" будет указателем, но "b" - нет
var a, b *int; // В Go обе переменные будут указателями

Go - язык, созданный программистами и для программистов. Это проявляется во всем, начиная от обрамления блоков кода в стиле Си, неявного объявления типов, отсутствии необходимости ставить точку с запятой после каждого выражения и заканчивая такими архитектурными решениями, как отсутствие механизма исключений и полноценных классов (они были созданы для упрощения жизни, но вместо этого приводят к запутыванию кода). Основная идея языка в том, чтобы быть инструментом, который позволяет писать программы, вместо того, чтобы думать о том, заработают ли они вообще (эта черта свойственна Си и, в еще большей степени, C++).

Средства параллельного программирования

Встроенные средства параллельного программирования - это самая сильная черта Go, и здесь среди языков общего назначения ему просто нет равных (за исключением разве что Limbo, но он привязан к ОС Inferno). И выигрыш здесь не столько в том, что эти средства встроены в сам язык, сколько в том, что они реализуют очень простую и эффективную модель, полностью следующую теории взаимодействующих последовательных процессов (CSP). Читатели, знакомые с Occam и Limbo, должны хорошо понимать все преимущества CSP, а для остальных поясню. Вместо того, чтобы городить огород из потоков, блокировок, мьютексов и прочих систем синхронизации, которые делают параллельное программирование невыносимой мукой и приводят к изданию многостраничных книг о том, как писать многопоточные приложения, автор CSP Тони Хоар предлагает простое и элегантное решение: позволить приложению в любой момент создать новую нить, которая сможет общаться с родителем и другими нитями с помощью отправки синхронных сообщений.

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

  1. Создание переменной-канала.
  2. Определение функции, которая принимает переменную-канал в качестве аргумента, а в своем теле содержит код, который должен быть выполнен в отдельной нити. В конце функция должна отправить результат своего выполнения в канал (это делается с помощью специального оператора).
  3. Запуск функции в отдельном потоке с помощью ключевого слова "go".
  4. Чтение из канала.

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

Пример

Один из моих любимых примеров, демонстрирующих мощь языка Go, - это реализация таймера, который выполняется в отдельном потоке и "стучит" основному потоку через определенные интервалы времени, в течение которых уходит в сон. Код этой программы, написанный на одном из "классических" языков программирования, выглядел бы громоздким и запутанным, но Go позволяет сделать его простым и красивым.

Код нашей программы:

1 package main
2
3 import "time"
4 import "fmt"
5
6 func timer(ch chan string, ns, count int) {
7 for j:= 1; j <= count; j++ {
8 time.Sleep(int64(ns))
9 if j == count {
10 fmt.Printf(" Отправляю последнее сообщение...n")
11 ch <- "стоп!"
12 } else {
13 fmt.Printf(" Отправляю...n")
14 ch <- "продолжаем"
15 }
16 fmt.Printf(" Отправил!n")
17 }
18 }
19
20 func main() {
21 var str string
22
23 ch:= make(chan string)
24 go timer(ch, 1000000000, 10)
25
26 for {
27 fmt.Printf(" Принимаю...n")
28 str = <-ch
29 if str == "стоп!" {
30 fmt.Printf(" Принял последнее сообщение, завершаю работу.n")
31 return
32 } else {
33 fmt.Printf(" Принято!n")
34 }
35 }
36 }

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

Результат работы программы после пяти итераций цикла

На первый взгляд листинг очень напоминает код программы, написанной на языке Си, C++ или даже Java, но при более детальном изучении становятся видны различия - Go унаследовал от Си только базовый синтаксис, в то время как большинство ключевых слов и лексика изменились. Исходный код начинается с ключевого слова package, следом за которым идет имя пакета, к которому этот код относится. Все запускаемые пользователем программы должны иметь имя main, тогда как библиотеки могут иметь произвольное имя, которое будет использовано для доступа к ее функциям и переменным после импортирования. При этом для пометки, должна ли функция или переменная быть экспортируемой, используется верхний регистр: все объекты, имена которых начинаются с большой буквы, будут экспортированы, остальные останутся приватными.

В строках 3 и 4 происходит импортирование пакетов time и fmt, функции которых понадобятся нам позже. Импортирование пакетов во многом очень похоже на включение в программу заголовочных файлов, как это делается в Си и C++, с тем исключением, что Go, во-первых, следит за пространством имен и все импортированные функции, переменные и типы данных будут иметь префикс в виде имени пакета, а во-вторых, не требует наличия самих заголовочных файлов. Никакой возни с хидерами и пространством имен!

Со строки 6 начинается описание функции timer() нашего главного действующего лица. В последующем коде она будет отправлена в отдельный поток, и большую часть времени проведет во сне, а просыпаясь, будет отчитываться головному потоку. Чтобы сделать это, ей нужен доступ к каналу, поэтому первый аргумент функции - это ch типа "канал для передачи строк". Также ей нужно знать временной отрезок, в течение которого она может спать, и то, сколько раз она сможет это сделать. Поэтому второй и третий аргументы - это ns и count типа int. Обрати внимание на форму описания аргументов. В отличие от Си, в Go сначала идет имя переменной, и лишь после - ее тип (что логично и согласуется с системой мышления человека: "переменная такая-то такого-то типа"). Тип возвращаемого функцией значения в Go следует помещать в конец, сразу за закрывающей скобкой (что, кстати, тоже логично). При этом, если функция должна возвращать несколько значений (в Go это возможно), их типы и (опционально) имена должны быть перечислены через запятую и обрамлены скобками. У нашей функции возвращаемого значения нет - уйдя в отдельный поток, она так или иначе ничего вернуть не сможет. Функция должна повторить процедуру "сон - отчет" указанное в переменной count число раз, поэтому в строке 7 начинается цикл for, запись которого полностью аналогична своему собрату в языке Си, за исключением отсутствия скобок.

Чтобы отправить поток timer в сон мы используем функцию Sleep (строка 8) из ранее импортированного пакета time. Ее аргумент, задающий длительность сна, должен иметь тип int64 (аналогичный типу long в Си), поэтому мы должны использовать приведение типов, компилятор не сделает это за нас (и правильно, мы умнее). Чтобы головной поток знал, когда поток timer завершится, и смог обработать эту ситуацию, timer должен предупредить его. Поэтому в строках с 9 по 15 происходит проверка на достижение максимального числа повторений сна. Для этого используется стандартный оператор if, который со времен Си остался неизменным, но так же, как и for, потерял скобки. Если это последнее повторение, на экран выводится "Отправляю последнее сообщение...", а в канал поступает сообщение "Стоп!", в противном случае на экране появится "Отправляю сообщения...", а в канал пойдет "Продолжаем". Каналы в Go типизированы, поэтому в канал ch, объявленный с типом chan string, можно отправить только строку (проверка типов в Go осуществляется во время компиляции, поэтому ошибки легко отловить). В строке 16 поток подтверждает отправку сообщения с помощью печати строки "Отправил!" на экран.

Как и в Си, в Go индикатором начала программы является функция main (строки с 20 по 36), в рамках которой будет выполняться основной поток. Все, что должна сделать наша функция main, это создать новый канал, передать его функции timer, отправить его в отдельный поток и ждать результатов.

Чтобы получать сообщения из канала, понадобится переменная-приемник. Эту роль будет выполнять переменная str типа string, объявленная в начале функции с помощью ключевого слова var (ее значением автоматически станет nil, что эквивалентно NULL в Си). Для создания канала используется встроенная функция make() (строка 23), которая просто выделяет память под указанный тип данных и инициализирует его нулевым значением. Кроме каналов с помощью make можно создавать ассоциативные массивы и срезы, для выделения памяти используется new(). Мы не можем просто объявить переменную типа chan string и работать с ней, потому что буфер, используемый для хранения передаваемых по каналу данных, не будет выделен. Также обрати внимание на неявное объявление переменной ch, которое происходит с помощью оператора ":=" (типизация при этом сохраняется, переменная будет иметь тип присваиваемого значения). В строке 24 timer наконец-то отправляется в отдельный поток. Причем делается это с помощью одного-единственного ключевого слова - go.

Теперь, когда timer был отправлен выполнять свое задание, головному потоку остается только ждать сообщений. Для приема сообщений из потока в Go используется уже описанный ранее оператор "<-", который теперь следует направить "из потока в принимающую переменную":

Но если бы мы добавили в код только одну эту строку, то головной поток продолжил бы работать после получения первого сообщения и в конце концов завершился, не обработав остальные сообщения. Поэтому нам нужен бесконечный цикл. Он занимает строки с 26 по 35. Go не имеет в своем составе "настоящего" while, поэтому, если требуется создать условный оператор цикла, то следует просто поместить условие после ключевого слова for и не париться (или вообще ничего не указывать, как это сделал я).

При каждой итерации цикла в переменную str будет записываться сообщение, пришедшее от потока timer, и, в зависимости от содержимого сообщения, будет выбираться тот или иной вариант дальнейших действий. Обрати внимание, язык позволяет спокойно сравнивать строки без всяких подсобных функций. Кроме того, ты можешь получать их срезы и копии (на манер python или ruby) и вычислять длину с помощью ключевого слова len (все это справедливо и в отношении массивов).

Для запуска программы потребуется компилятор, который можно скачать с официального сайта языка (правда пока доступны только версии для UNIX, Plan9 и MacOS X). Если ставить его не хочется (или у тебя Windows), программу можно запустить, используя специальную форму на сайте golang.org (правда, из-за ограничения на длительность работы программы продолжительность сна потока timer придется сильно сократить). Это все.

Постойте, но ведь это не многопоточность?

Да, ты наверняка заметил, что из-за блокировок каналов даже на многоядерном процессоре одновременно активным будет только один поток нашей программы, тогда как другой будет ждать отправку/прием сообщения. Это действительно так, и для решения этой проблемы Go располагает рядом средств.

  1. Каналы можно проверять на наличие сообщений. Если строку " str = <-ch " заменить на " str, ok = <-ch ", то головной поток не будет заблокирован, даже если в канале нет сообщения. Вместо этого в переменную ok будет записано значение false и работа потока продолжится. Естественно, дальше можно поместить проверку на " ok == false " и успеть выполнить какую-то полезную работу, а затем начать новую итерацию цикла и вновь попробовать получить значение. Кстати, таким же образом можно выполнить проверку в потокеотправителе:

ok:= ch <- "Продолжаем"

  1. Каналы в Go могут быть буферизированы, то есть уметь накапливать определенное количество сообщений до того, как отсылающая сторона будет заблокирована. Для этого достаточно всего лишь добавить один дополнительный аргумент в вызов функции make:

ch:= make(chan string, 10) // создать канал с буфером в 10 позиций

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

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

select {
case str = <-ch1:
// обрабатываем сообщение от первого потока
case str = <-ch2:
// обрабатываем сообщение от второго потока
case str = <-ch3:
// обрабатываем сообщение от третьего потока
}

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

Оператор select очень широко используется в Go-программировании, именно с его помощью принято создавать "диспетчеры сообщений", которые разветвляют программу на множество потоков сразу после старта, а затем входят в бесконечный цикл и начинают обработку пришедших от них сообщений. В операционной системе Inferno (все приложения которой написаны на Go-предке Limbo) таким образом реализован многооконный графический интерфейс.

Выводы

Go пока еще молодой, но очень перспективный язык, при создании которого был учтен более чем тридцатилетний опыт в области разработки операционных систем и языков программирования (Роб Пайк двадцать лет занимался исследованиями в области многопоточного программирования, в течение которых были созданы языки Squeak, NewSqueak и Limbo). Go производителен, дружелюбен к программистам и красив.

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

Golang, или Go - язык программирования, начало которого было положено в 2007 году сотрудниками компании Google: Робертом Гризмером, Робом Пайком и Кеном Томпсоном.

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

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

Размер программ на Go начинается с 3 строк и может достигать нескольких миллионов, записанных в один или несколько файлов с расширением.go . Современные текстовые редакторы, например, поддерживают его синтаксис.

Настройка окружения

Для начала скачаем 64-битную версию Go c официального сайта . В зависимости от используемой операционной системы выполняем следующие действия.

UNIX / Linux / MacOS X / FreeBSD

Извлекаем скачанный архив в папку /usr/local/go . Например:

Tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz

Добавляем папку /usr/local/go/bin в переменную окружения PATH:

Export PATH=$PATH:/usr/local/go/bin

Windows

Используем MSI файл и следуем инструкциям. По умолчанию инструменты Go размещаются в папке С:\Go . При установке папка C:\Go\bin автоматически добавится в системную переменную PATH .

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

Проверяем корректность установки, создав и выполнив файл C:\Projects\Go\test.go:

Package main import "fmt" func main() { fmt.Println("Привет, Tproger!") }

Синтаксис

Пакеты

Каждая программа на языке Go состоит из пакетов (packages). Пакет main - главный, с него начинается выполнение программы. В приведённом выше примере импортируется пакет fmt .

Импорт

Импорт пакетов можно описать двумя способами.

Import "fmt" import "math"

Import("fmt" "math")

Функции

Общая форма определения функции выглядит следующим образом:

Func function_name([список параметров]) [возвращаемые типы данных] { тело функции }

Количество и тип входных аргументов может быть любым. Для примера опишем функцию add с двумя входными параметрами формата int:

Package main import "fmt" func add(a int, b int) int { return a + b } func main() { fmt.Println("Сумма равна ", add(10, 19)) }

Выполним этот код и получим следующий результат:

Переменные

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

Var [перечень переменных] [тип данных];

С помощью оператора var определяем перечень переменных, причём тип данных указываем в конце выражения.

Объявление возможно как на уровне пакета, так и на уровне функции. Рассмотрим пример:

Package main import "fmt" var node, golang, angular bool func main() { var x int fmt.Println(x, node, golang, angular) }

Выполним этот код и получим следующий результат:

Оператор цикла

В языке Go один оператор цикла - это for , который повторяет список инструкций заданное количество раз. Цикл состоит из трёх выражений:

  1. Инициализация. Выполняется перед первой итерацией.
  2. Условие. Вычисляется перед каждой итерацией.
  3. Инкремент. Выполняется после каждой итерации.

Общая форма цикла выглядит так:

For [условие | (инициализация; условие; инкремент) | диапазон] { [тело цикла] }

При инициализации кратко объявляются переменные, которые доступны только в пределах цикла.

Цикл останавливает свою работу, когда условие принимает значение false .

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

Рассмотрим пример программы:

Package main import "fmt" func main() { sum:= 0 for i:= 0; i < 8; i++ { sum += i } fmt.Println("Сумма равна ", sum) }

Выполним этот код и получим следующий результат:

Условный оператор

Форма определения условного оператора в Go выглядит следующим образом:

If [условие] { ... }

Если выражение в поле условия истинно, код, размещённый между фигурными скобками, выполнится.

Условие описывается значением, переменной или выражением. Например:

  • true - выполняется всегда;
  • a < 10 - выполняется, когда a меньше 10 ;
  • (a < b) || (a < c) - выполняется, когда a меньше b или a меньше c ;
  • (a < b) && (a < c) - выполняется, когда a меньше b и a меньше c .

Рассмотрим пример программы:

Package main import ("fmt") func main() { if true { fmt.Println("Это выражение выполнится всегда") } if false { fmt.Println("Это выражение не выполнится никогда") } }

Выполним этот код и получим следующий результат:

Массивы

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

Чтобы объявить массив, необходимо указать тип и требуемое количество элементов следующим образом:

Var наименование_переменной [размер] тип_переменной

Например, чтобы создать массив balance , состоящий из десяти элементов типа float32 , используем следующее выражение:

Var balance float32

Если значения элементов известны, для определения массива допустимо использовать следующую конструкцию:

Var balance = float32{1000.0, 2.0, 3.4, 7.0, 50.0}

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

Рассмотрим пример программы:

Package main import "fmt" func main() { var a string a = "Привет" a = "Tproger" fmt.Println(a, a) fmt.Println(a) primes:= int{2, 3, 5, 7, 11, 13} fmt.Println(primes) }

Выполним этот код и получим следующий результат:

Срезы

Срезы (Slices) в Go - абстракция над массивами. Хотя встроенных способов увеличить размер массива динамически или сделать вложенный массив в Go нет, срезы убирают это ограничение. Они предоставляют полезные функции и широко используются программистами.

Объявить срез можно как массив, но без указания размера или с помощью функции make:

Var numbers int /* срез неопределённого размера */ /* numbers = int{0,0,0,0,0} */ numbers = make(int,5,5) /* срез длиной и ёмкостью равной 5 */

Размер массива фиксирован, а у среза изменяемый. По сути, срез - более общий вид массива.

Тип T - срез с элементами типа T .

a - срез 5 элементов массива a .

Рассмотрим пример программы:

Package main import "fmt" func main() { primes:= int{2, 3, 5, 7, 11, 13} fmt.Println(primes) var s int = primes fmt.Println(s) }

Выполним этот код и получим следующий результат:

Структуры

Структура (structure) - пользовательский тип данных, который кроме прочего комбинирует элементы разных типов. Чтобы объявить структуру, используем выражения type и struct .

Struct определяет тип данных, которому соответствует два и более элементов.

Type связывает заданное имя с описанием структуры.

Форма описания выглядит следующим образом:

Type struct_variable_type struct { member definition; member definition; ... member definition; }

Как только структура типа определена, он может использоваться при объявлении новых переменных:

Variable_name:= structure_variable_type {значение1, значение2...значениеN}

Чтобы получить доступ к элементам структуры, используем оператор доступа к элементу. Рассмотрим на примере:

Package main import "fmt" type Vertex struct { X int Y int } func main() { v:= Vertex{1, 2} v.X = 4 fmt.Println(v.X) }

Выполним этот код и получим следующий результат:

Почему именно Golang?

Концепция языка Golang разработана на основе опыта решения повседневных задач и не преследует цель сделать прорыв в программировании. Кроме того, Go не реализует ряд функций, которые делают другие языки (C++, Java и Python) настолько мощными. Но есть три причины, чтобы задуматься об использовании этого языка.

Читабельность

Как только привык к синтаксису Go, прочесть чужой код - тривиальная задача.

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

Скорость

За короткий промежуток времени можно написать быструю программу.

Производительность работы пакета на Go, возможно, будет меньше, чем при использовании С, зато скорость компиляции для больших программ будет выше. Для большинства проектов - приемлемый компромисс, причём при необходимости достаточно просто переписать код на языке Go.

Меньше ошибок

Большинство ошибок возникают в непроверенном или сложном коде.

Go предоставляет удобные средства для тестирования. Кроме того, строгая типизация устраняет ошибки вроде случайного сравнения количества яблок с количеством груш, которые определяются ещё на этапе компиляции.

Заключение

Если хотите погрузиться глубже в изучение, посмотрите нашу , а главное - начните создавать маленькие программы, чтобы на практике понять тонкости языка. Удачи!

Что на нём пишут?

И что можно написать?

Что угодно. На самом деле что угодно, но, в силу своих плюсов он очень популярен для разработки сервер-сайда (бэкенда).

Стоит ли его учить?

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

  1. Для себя, своего стартапа
  2. Продавать готовый продукт
  3. Выполнить заказ, если заказчику подходят плюсы этого языка (придётся объяснять/убеждать)

Или он скоро исчезнет?

Как уже писали - исчезнуть он не может, т.к. OpenSource. Т.е. никто не отберёт у Вас написанное на нём, максимум будет ухудшаться развитие/поддержка, во что слабо верится, т.к у языка очень существенные плюсы.

Плюсы или «почему я выбрал Go»

Производительность

По производительности для веб (готовые фреймворки) Go проигрывает только Java и С/С++ и наравне с node.js. При этом потребление ресурсов существенно ниже, чем у Java и производительность намного больше, чем у Python/Ruby.

Многопоточность

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

Простота

Он очень прост в освоении. Мне кажется даже элементарен, особенно если есть основа из Python/JavaScript. Есть также довольно занятная модель наследования, которая, как мне кажется более прозрачна чем классическое ООП, но немного непривычна поначалу.

Надёжность

Язык этот компилируемый и статически типизированный. Что даёт выявление многих ошибок задолго до продакшена. К примеру такие ошибки в Python выявляются только непосредственным тестированием и, если тестов нет, то шансов положить систему очень много. Тут же это исключается на этапе компиляции.

Скорость компиляции

И, наконец, одна из главнейших фишек - не стоит бояться типизации и компиляции. В 145% случаев Вам не придётся объявлять тип переменой в коде - он задаётся автоматически при присвоении ей значения. Объявлять переменные заранее также не нужно.

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

Итого

Т.е. мы имеем плюсы из двух миров - скорость компиляции/запуска интерпретируемого и надёжность компилируемого языков. Плюc сверху производительность, мультипарадигменность (можно писать в функциональном стиле), простота и низкое ресурсопотребление.

Подходит ли это Вам - решать не мне. Мне - подходит, и я считаю его очень хорошим выбором для высоконагруженных сервисов (и не только).

Стефан Нильссон преподает информатику в Королевском технологическом институте Стокгольма и очень много пишет о языке Go. Предлагаем вам перевод его статьи Why Go? - Key advantages you may have overlooked , где он рассказывает о главных плюсах языка. Статья ориентирована на читателей, уже знакомых с основами программирования, в том числе на Java и/или Python.

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

Go - мой основной язык с 2012 года. До этого, с 1998 года, я использовал Джаву, а еще раньше - Си. Python нужен мне главным образом в преподавательской работе.

Заниматься программированием я начал в далеком 1978 году. Тогда я писал для калькулятора TI-57 , с его программной памятью на 50 шагов и 8 регистрами.

Минимализм

Go - минималистичный язык , и это (по большей части) очень хорошо.

Задел на будущее

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

Другой пример: Go не скомпилирует программу, если она требует каких-то пакетов (через import), но в реальности не использует их в коде . Такой подход повышает ясность кода, а в долгосрочной перспективе - его производительность.

Подозреваю, что создатели Go нарочно усложнили некоторые вещи. Например, надо очень постараться, чтобы поймать исключение (панику). Причем, чтобы переступить через типобезопасность, вам придется пометить свой код ключевым словом unsafe.

Сравнение с Python

В Python фрагмент кода del a[i] удаляет элементы с индексом i из списка a. Этот код, конечно, вполне читаем , но не прозрачен : легко упустить из вида, что временная сложность алгоритма представлена как O(n), где n - число элементов списка.

У Go нет такой полезной функции. Это не так удобно , зато более прозрачно . Если вы копируете элемент списка, вам нужно явно указать это в коде. Смотрите пример кода: 2 способа удалить элемент из среза в Go. Но можно и проще - с помощью аppend .

Сравнение с Java

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

  • Циклические зависимости могут вести к нежелательным последствиям. В отличие от кода на Java, Go-программа с зацикленной инициализацией не скомпилируется.
  • Программа на Go завершает работу только из функции main. Java-приложение закрывается после завершения всех пользовательских потоков, не являющихся демонами.

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

Совместимость

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

Для первой версии Go были кратко и сжато сформулированы гарантии совместимости для языкового «ядра» и стандартных библиотек: программы на Go, которые работают сегодня, должны работать и с будущими версиями Go 1. До сих пор обратная совместимость соблюдается безукоризненно.

Go - это проект с открытым кодом и BSD-подобной лицензией , которая разрешает коммерческое использование, внесение изменений, распространение и личное пользование.

Права интеллектуальной собственности принадлежат

К сожалению, на горизонте сгущаются тучи. Причиной тому - судебное разбирательство между Oracle America и Google о сущности компьютерного кода, авторском праве и новой модели лицензирования Java от Oracle.

Производительность

Снаружи Go неброский, но под капотом у него - прекрасно отлаженный движок.

Бессмысленно обсуждать производительность вне контекста. Такие параметры программы, как время исполнения и потребление памяти, сильно зависят от алгоритмов, структур данных, входных данных, мастерства программиста, операционной системы и «железа».

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

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

Объем и скорость генерации машинного кода зависят от целевой архитектуры. Генерация кода Go довольно и поддерживает все основные ОС (Linux, macOS, Windows) и архитектуры (Intel x86/x86-64, ARM64, WebAssembly, ARM и др.). Поэтому от go-приложений можно ждать производительности на уровне C++ или Java. Выигрыш относительно интерпретируемого кода на Python может быть огромным.

В Go есть сборщик мусора , что предотвращает утечки памяти. Причем задержка в работе сборщика минимальна. На деле вы можете даже не замечать, что «поток-мусорщик» запущен.

Практически все стандартные библиотеки исполнены очень качественно: их код оптимизирован по эффективным алгоритмам. К примеру, регулярные выражения в Go работают настолько хорошо, что время исполнения напрямую зависит от объема ввода. К сожалению, с Java и Python все иначе .

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

Похожие публикации