Основные принципы программирования: конкурентность
34К открытий35К показов
Рассказывает Аарон Краус
В третьей статье цикла “Принципы программирования” мы поговорим о конкурентности (concurrency). Конкурентность — это свойство систем (программы, сети, компьютера и т.д.), допускающее одновременное выполнение нескольких вычислительных процессов, которые могут взаимодействовать друг с другом. Вычисления запускаются, проходят и завершаются в пересекающихся промежутках времени; они также могут происходить абсолютно одновременно (параллелизм), но это не обязательно.
Конкурентность в программировании
Конкурентность реализована в логике программирования таким образом, что она явно устанавливает отдельные точки исполнения вычислений или процессов, называемые управляющими потоками. Они позволяют этим вычислениям избежать ожидания завершения всех остальных вычислений — как это происходит в случае последовательного программирования.
Конкурентные и параллельные вычисления
Хотя и считается, что конкурентные вычисления включают в себя параллельные, у них есть существенные отличия.
Параллельные вычисления используют более одного вычислительного ядра, поскольку все управляющие потоки работают одновременно и занимают весь рабочий цикл ядра на время исполнения — именно поэтому параллельное вычисление невозможно на одноядерном компьютере. В этом они отличаются от конкурентных вычислений, которые фокусируются на пересечениях жизненных циклов вычислений. Например, этапы выполнения процесса могут быть разбиты на временные промежутки, и если процесс не заканчивает своё существование до конца промежутка, он приостанавливается, предоставляя другому процессу возможность работать.
Зачем нужно конкурентное программирование?
Главным преимуществом этого подхода является максимально возможное использование ресурсов системы. В начале 2000-ых стало популярным использование многоядерных процессоров, пришедших на смену одноядерным, пускай и с очень мощным на то время ядром. Это в первую очередь позволяет оптимизировать время выполнения программы путём разделения нагрузки на ядра. Я не буду углубляться в эту тему — принципы синхронизации процессов и потоков различны в разных ОС — но вы можете почитать об этом, например, здесь.
В современных языках программирования принцип конкурентности обычно реализован в виде процесса многопоточности. Многопоточность позволяет программе работать в нескольких потоках, предоставляя преимущества параллелизации (быстрое исполнение, эффективное использование ресурсов и т.д.), но она также сохраняет и её недостатки (о них ниже), поэтому некоторые языки используют механизм, названный Global Interpreter Lock (GIL). Обычно его можно встретить в стандартных реализациях Python и Ruby (CPython и Ruby MRI соответственно), он предотвращает одновременное исполнение более чем одного потока — даже на многоядерных процессорах. Это похоже на недостаток, но GIL нужен для предотвращения потоконебезопасных активностей. Реализации с GIL обычно повышают скорость работы однопоточных программ и облегчают интеграцию с библиотеками C, но за это приходится платить возможностями многопоточности.
Проблемы конкурентного программирования
Поскольку идеей конкурентности является одновременное исполнение вычислений, может случиться так, что эти отдельные вычисления получат доступ к разделёнными между ними ресурсам и случайно изменят их (так называемое потоконебезопасное поведение). В таких случаях используются арбитры, но такой тип активности может создать неопределённость и привести к взаимной блокировке и голоданию.
Всё это делает координацию конкурентных задач очень важной, поскольку даже в тех местах, над которыми у разработчика почти нет власти — например, в распределении памяти в стеке и куче — может возникнуть неопределённость.
Прим. перев. Рекомендуем также почитать наши материалы по теме многопоточного программирования:
- Безопасность потоков в С++;
- Обработка исключений в многопоточных приложениях;
- Задача с разбором решения: “Разработайте класс, обеспечивающий блокировку так, чтобы предотвратить возникновение мертвой блокировки”.
Будущее конкурентного программирования
Конкурентное программирование — это очень мощный инструмент, даже с учётом его недостатков. Несколько языков предоставляют феноменальную поддержку инструментов для использования этого принципа: больше всего это проявляется в API языка Go, созданного в Google. Разработчики других языков, например, Python и Ruby, видят отрицательные стороны конкурентности и поэтому используют GIL по умолчанию. Так или иначе, если вы пишете приложение, которое использует принципы конкурентности, обязательно уделите особое внимание планированию, иначе вы рискуете повредить данные.
34К открытий35К показов