Написать пост

Основные принципы программирования: конкурентность

Аватар Иван Бирюков

Обложка поста Основные принципы программирования: конкурентность

Рассказывает Аарон Краус 

В третьей статье цикла “Принципы программирования” мы поговорим о конкурентности (concurrency). Конкурентность — это свойство систем (программы, сети, компьютера и т.д.), допускающее одновременное выполнение нескольких вычислительных процессов, которые могут взаимодействовать друг с другом. Вычисления запускаются, проходят и завершаются в пересекающихся промежутках времени; они также могут происходить абсолютно одновременно (параллелизм), но это не обязательно. 

Конкурентность в программировании

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

Конкурентные и параллельные вычисления

Хотя и считается, что конкурентные вычисления включают в себя параллельные, у них есть существенные отличия.

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

Зачем нужно конкурентное программирование?

Главным преимуществом этого подхода является максимально возможное использование ресурсов системы. В начале 2000-ых стало популярным использование многоядерных процессоров, пришедших на смену одноядерным, пускай и с очень мощным на то время ядром. Это в первую очередь позволяет оптимизировать время выполнения программы путём разделения нагрузки на ядра.  Я не буду углубляться в эту тему — принципы синхронизации процессов и потоков различны в разных ОС — но вы можете почитать об этом, например, здесь.

В современных языках программирования принцип конкурентности обычно реализован в виде процесса многопоточности. Многопоточность позволяет программе работать в нескольких потоках, предоставляя преимущества параллелизации (быстрое исполнение, эффективное использование ресурсов и т.д.), но она также сохраняет и её недостатки (о них ниже), поэтому некоторые языки используют механизм, названный Global Interpreter Lock (GIL). Обычно его можно встретить в стандартных реализациях Python и Ruby (CPython и Ruby MRI соответственно), он предотвращает одновременное исполнение более чем одного потока — даже на многоядерных процессорах. Это похоже на недостаток, но GIL нужен для предотвращения потоконебезопасных активностей. Реализации с GIL обычно повышают скорость работы однопоточных программ и облегчают интеграцию с библиотеками C, но за это приходится платить возможностями многопоточности.

Проблемы конкурентного программирования

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

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

Прим. перев. Рекомендуем также почитать наши материалы по теме многопоточного программирования:

Будущее конкурентного программирования

Конкурентное программирование — это очень мощный инструмент, даже с учётом его недостатков. Несколько языков предоставляют феноменальную поддержку инструментов для использования этого принципа: больше всего это проявляется в API языка Go, созданного в Google. Разработчики других языков, например, Python и Ruby, видят отрицательные стороны конкурентности и поэтому используют GIL по умолчанию. Так или иначе, если вы пишете приложение, которое использует принципы конкурентности, обязательно уделите особое внимание планированию, иначе вы рискуете повредить данные.

Следите за новыми постами
Следите за новыми постами по любимым темам
32К открытий32К показов