Это одиннадцатая статья из моей еженедельной серии 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 дает нам для использования. На следующей неделе я буду говорить о каналах, мьютексах и условиях гонки. Тогда увидимся!