Это одиннадцатая статья из моей еженедельной серии Learning Go. На прошлой неделе я говорил о Сортировке данных в Go. На этой неделе я расскажу о том, как работает параллелизм в Go. Прежде чем я действительно углублюсь в тему параллелизма, я чувствую, что мне нужно провести некоторые различия между параллелизмом и параллелизмом, поскольку они часто путают друг с другом. Я также объясню несколько частей языка Go, которые позволяют нам использовать Concurrency. Эти части - процедуры Go и утверждения Go.

Параллелизм

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

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

Параллелизм

возможность выполнять несколько вычислений одновременно (одновременно)

  • Создан, чтобы выполнять несколько задач одновременно
  • Возможность выполнять несколько задач в многоядерном процессоре
  • Требуется многоядерный процессор

Параллелизм в Go

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

Go Routines

  • Мультиплексный
  • Используется с функциями или методами
  • Используется с ключевым словом go

Заявления Go

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

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

Горутины

легкий поток выполнения

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

  • Горутины неблокирующие (асинхронные)
  • Благодаря асинхронности несколько горутин могут выполняться одновременно (несколько частей выполняются одновременно, не влияя на конечный результат)
  • Если вы хотите дождаться завершения горутины, прежде чем продолжить, вы можете использовать WaitGroup (мы расскажем об этом позже в этой публикации)

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

Давайте разберемся, что происходит:

Краткое примечание: мы импортируем пакет time, потому что нам нужно подождать секунду, чтобы наши горутины завершили свою работу. Помните, они не блокирующие (синхронные); поэтому нам нужно подождать , пока они закончат свои вычисления.

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

Затем мы создаем две функции, countToFive и countToThree, обе они ожидают одного параметра wasCalledBy, который имеет тип string.

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

Внутри func main я вызываю функцию countToFive напрямую, без использования горутины

Как говорится в аргументе, я не использую ключевое слово go и не создаю Goroutine; следовательно, этот код будет синхронным и заблокирует наш поток выполнения.

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

Затем я запускаю еще одну Goroutine, помещая ключевое слово go перед идентификатором функции.

Чтобы гарантировать завершение работы наших горутин, мы используем пакет time, чтобы спать на одну секунду.

Что вы ожидаете увидеть в наших журналах? В каком порядке, по вашему мнению, будут выполняться эти горутины?

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

Первые 5 строк не должны вас удивлять, мы вызываем функцию без использования Goroutine; следовательно, он работает синхронным (блокирующим) способом.

Однако следующие несколько строк должны вызвать недоумение. Вы заметили, что наша countToThree функция записала элемент раньше, чем countToFive?

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

WaitGroups

Использование WaitGroups для ожидания завершения нескольких горутин - обычная практика при использовании Go. WaitGroup - это тип, который является частью пакета sync.

В WaitGroups есть несколько функций, которые вы будете часто использовать. Наиболее важными из них являются Add и Done. Позвольте мне рассказать вам, как их использовать.

Давайте рассмотрим, что происходит, построчно:

Как я упоминал ранее, поскольку WaitGroup вызывается из пакета sync, нам нужно убедиться, что мы импортируем его

Затем мы создаем функцию с идентификатором countRoutine, которая имеет два параметра: upTo типа string и wg, который является указателем на sync.WaitGroup

Примечание. WaitGroups можно передавать функциям только как указатель

Внутри этой функции мы создаем цикл for и выполняем итерацию, пока не достигнем значения upTo, которое мы передаем в функцию. Чтобы WaitGroup знала, что Goroutine завершена, мы запускаем функцию Done()

Затем мы создаем функцию с идентификатором count с одним параметром upTo типа int. У нас есть такой же цикл for внутри этой функции, с той лишь разницей, что мы не используем WaitGroup, потому что это не Goroutine.

Внутри функции main мы создаем переменную с помощью ключевого слова var и присваиваем этой переменной идентификатор wg типа sync.WaitGroup.

Чтобы сообщить среде выполнения Go о нашей WaitGroup, мы должны добавить ее. Это легко сделать с помощью функции Add(), которая принимает аргумент типа int, который указывает, сколько WaitGroups вы хотите добавить. В этом примере у нас есть только одна Goroutine, поэтому мы просто добавим ее:

Затем мы используем ключевое слово go для запуска countRoutine как Goroutine и передаем 10 в качестве аргумента upTo и указатель WaitGroup (&wg) в качестве аргумента wg.

Мы вызываем функцию count, которая будет синхронной, блокирующей функцией.

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

Как я упоминал ранее, мы сообщаем среде выполнения Go, что наша Goroutine завершена, путем вызова функции Done() в конце нашей Goroutine. Как только мы это сделаем, среда выполнения Go знает, что может выйти из программы.

В итоге

Используя возможности горутин в сочетании с WaitGroups, мы можем писать параллельный код на Go. Довольно круто, да? Я разбил эту тему на две части, потому что у меня есть еще много интересного, что нужно показать вам о написании параллельного кода Go и инструментах, которые Go дает нам для использования. На следующей неделе я буду говорить о каналах, мьютексах и условиях гонки. Тогда увидимся!