Конкурентность и Параллелизм
Last updated
Was this helpful?
Last updated
Was this helpful?
Что такое конкурентность?
В компьютерном программировании конкурентность — способность компьютера справляться с множеством разных задач одновременно. Давайте возьмем для примера использование браузера. Если вы гуляете по сети, происходит много вещей одновременно. Например идет скачивание файла в то время как вы слушаете музыку и скролите веб сайт в другой в кладке. Можно сказать что компьютер выполняет несколько задач одновременно. Если бы компьютеры так не умели, нам бы пришлось ждать окончание скачивания файла, чтобы продолжить слушать музыку или скролить сайт. Архитектура ЦПУ устроена таким образом, что одно ядро может выполнять лишь одно действие в единицу времени. В наши дни на рынке все еще есть множество одноядерных процессоров. Но если у вас компьютер с одноядерным процессором, то неужели вы не сможете скачивать файл, слушать музыку и листать мемы в другой вкладке одновременно? Конечно сможете, а достигается это с помощью конкурентности. Давайте посмотрим диаграмму, которая демонстрирует как одноядерный ЦПУ справляется с примером с браузером.
Как вы можете увидеть, все задачи разбиваются на кусочки, которые делятся по приоритетности, а ЦПУ постоянно переключается между ними, создавая иллюзию одновременного исполнения.
Что такое параллелизм?
А что если у нас многоядерный процессор? Как в таком случае распределяются задачи?
Одно ядро может выполнять одну задачу в единицу времени. Когда у нашего ЦПУ многоядерная архитектура, он может разделять задачи между ядрами, которые будут выполнятся одновременно т.е. параллельно. Обработка нескольких задач одновременно и есть параллелизм.
Устройство компьютерной программы
Для того чтобы понять модель конкурентности в Go, для начала нам стоит углубится в более низкоуровневые концепции и понять как на нашем компьютере выполняются программы. Когда вы пишите программы на разных языках типо С , Java, Go, Python, Scala, Ruby , Erlang и тд. ваши программы — просто набор символов в текстовом файле. После компиляции (или интерпретации в случае скриптовых языков), код языка превращается в набор инструкций в формате нулей и единиц, которые отправляются в ОС для выполнения.
Процессы
Когда инструкции после компиляции передаются в операционную систему, ОС создает процесс и аллоцирует (выделяет) под него адресное пространство, в котором находятся стэк (stack) и куча (heap), присваивает PID (process id) и делает другие важные вещи. Любой процесс обладает как минимум одним потоком, который называется главным потоком программы. Главный поток имеет возможность создавать другие потоки внутри этого процесса. По сути, процесс можно назвать контейнером, который собирает и аллоцирует все необходимые элементы и ресурсы ОС, которые в последствии могут быть переданы в потоки. А что же тогда такое потоки? Зачем они нужны и какова их роль?
Потоки Поток — легковесный процесс внутри самого процесса. Потоки делят между собой общую память, в то время как процессы не могут так просто обращаться к адресному пространству друг друга. Во время выполнения поток хранит все данные в области памяти, называемой стэком. Стэк создается во время “рантайма” процесса (время выполнения процесса) и зачастую имеет фиксированный размер 1–2 мб. Стэк выделяется под каждый поток отдельно и эта область памяти не делится между несколькими потоками. Существует также другой тип в области памяти, именуемый кучей (heap). Куча является характеристикой самого процесса, поэтому она делится между потоками. Приводя аналогию, можно сказать, что кухня в ресторане — это процесс. Задача кухни готовить различные блюда. А внутри этой кухни имеется много поваров и других рабочих — они являются потоками. Все они занимаются разными вещами, однако делают это на одной кухне и читают одну и ту же книгу с рецептами. На практике все это означает, что когда мы запускаем, к примеру, веб браузер — ОС создает новый процесс. Когда же мы начинаем скачивать файлы, проигрывать музыку и открывать новые вкладки, этот процесс создает множество разных потоков.
Мы с вами узнали, что конкурентность —справляться с несколькими задачами одновременно, а параллелизм — выполнять несколько задач одновременно. Задача здесь — это поток. Соответственно, когда несколько задач запускаются в конкурентной или параллельной манере — мы работаем с несколькими потоками. Это называется многопоточностью. Для того чтобы работать с несколькими потоками, ОС занимается планировкой потоков (или thread scheduling). Когда у нас есть несколько потоков, которые работают с одним и тем же участком памяти, у нас может возникать такое явление как race condition (состояние гонки). Это нужно постоянно держать в голове при разработке конкурентных приложений.
Конкурентность в Go
Наконец-то мы добрались до конкурентности в Go. В отличии от таких классических ООП языков, как, например,Java , в котором существует отдельный класс Thread , предоставляющий возможность создавать потоки внутри процессов, Go предоставляет ключевое слово go . Использование go перед вызовом функций или методов создает новую горутину. По своей сути горутина выполняется как поток, но по факту, является абстракцией над потоками. Когда мы запускаем программу на Go, райнтайм самого Go создает несколько потоков на ядре, на которых будут помещаться все горутины. Когда на одном потоке процесса происходит блокировка горутины, рантайм переключает контекст на другую горутину. По своей сути, это та же планировка потоков, только управляемая не ОС, а самим языком и является существенно быстрее. Для большинства случаев, советуется запускать программы, написаны на Go, на одном ядре. Однако в языке есть возможность распределить горутины между доступными ядрами системы с помощью runtime.GOMAXPROCS(n), где n— количество ядер.
Потоки и Горутины
Разобрав тему потоков и горутин, вы уже могли понять что между ними есть существенная разница. Перед вами небольшой список основных различий, однако если углубляться, их намного больше. Чтобы выделить на примере основные преимущества модели конкурентности в Go, представьте веб сервер, который обрабатывает 10000 запросов в минуту. Если вам нужно обрабатывать все запросы конкурентно, вам нужно создать 10000 потоков. Именно так и делает Apache Server. Если поток расходует 1MB памяти, то для обработки 10000 запросов нужно будет выделить ~10GB памяти! В Go вы также можете без проблем создать 10000 горутин для обработки запросов, под каждую с которых будет выделено 2KB памяти. Сами понимаете существенную разницу и возможности такой модели.
Итак, давайте резюмируем. Конкурентность — способность компьютера справляться с множеством разных задач одновременно, в то время как параллелизм — выполнять несколько задач одновременно. Когда мы запускаем программу на компьютере, создается новый процесс, для которого ОС аллоцирует свои ресурсы. Процесс состоит из потоков, которые выполняют разные задачи. В Go конкурентность достигается с помощью горутин, которые являются абстракцией над потоками, управляются самим рантаймом Go и очень легковесны. Конкурентное программирование никогда не было столь важным, как сегодня. Веб-серверы одновременно обрабатывают запросы тысяч клиентов. Приложения на планшетах и смартфонах визуализируют анимацию интерфейса пользователя и одновременно в фоновом режиме выполняют вычисления и сетевые запросы. Такой подход позволяет выжимать максимум из ресурсов и улучшать производительность современных систем.