Алгоритм пекарни
Алгоритм пекарни (англ. Bakery algorithm) — это алгоритм взаимного исключения для нескольких процессов (потоков), работающих в общей памяти, который гарантирует отсутствие взаимных блокировок, голодания и обеспечивает справедливое распределение ресурсов. Разработан американским учёным Лесли Лампортом в 1974 году. Название алгоритма происходит от аналогии с работой пекарни, где клиенты получают номерки и обслуживаются в порядке возрастания номеров.
История и контекст создания
В начале 1970-х годов, с развитием многозадачных операционных систем и многопроцессорных вычислительных систем, остро встала проблема синхронизации доступа к разделяемым данным. Существовавшие на тот момент решения, такие как алгоритм Деккера и алгоритм Петерсона, были рассчитаны только на два процесса. Лесли Лампорт, работавший в компании Xerox PARC, предложил обобщённый алгоритм, пригодный для произвольного числа процессов. Алгоритм был опубликован в 1974 году в статье «A New Solution of Dijkstra’s Concurrent Programming Problem» в журнале Communications of the ACM. Алгоритм пекарни стал одним из первых строго доказанных решений проблемы взаимного исключения для n процессов, не использующих аппаратных инструкций типа test-and-set или compare-and-swap.
Принцип работы
Алгоритм имитирует поведение очереди в пекарне: каждый процесс, желающий войти в критическую секцию, получает «номерок» (числовое значение), после чего ожидает, пока все процессы с меньшими номерками завершат свою работу. Номерки присваиваются в порядке возрастания, но не обязательно последовательно, что позволяет избежать сложностей с генерацией уникальных номеров в распределённой системе.
Основные переменные
Для реализации алгоритма используется два глобальных массива, разделяемых всеми процессами:
choosing[i]— булевый массив, показывающий, находится ли процессiв процессе выбора номера (1 — да, 0 — нет).number[i]— целочисленный массив, хранящий текущий номер процессаi(0 означает, что процесс не участвует в соревновании).
Псевдокод
Для процесса с индексом i (от 0 до N-1) алгоритм выглядит следующим образом:
``` while (true) { / Предпротокол / choosing[i] = 1; number[i] = max(number[0], number[1], ..., number[N-1]) + 1; choosing[i] = 0;
for (j = 0; j < N; j++) { while (choosing[j] == 1) { / ждать, пока процесс j выбирает номер / } while ((number[j] != 0) && ((number[j] < number[i]) || ((number[j] == number[i]) && (j < i)))) { / ждать / } }
/ Критическая секция / // ... работа с разделяемыми данными ...
/ Постпротокол / number[i] = 0;
/ Некритическая секция / // ... остальная работа процесса ... } ```
Пояснение работы
- Выбор номера: процесс устанавливает
choosing[i] = 1, вычисляет новый номер как максимальный среди всех текущих номеров плюс 1, затем сбрасываетchoosing[i] = 0. Флагchoosingнеобходим для предотвращения ситуации, когда два процесса одновременно читают номер друг друга и получают одинаковые значения. - Ожидание: процесс проверяет все остальные процессы (j от 0 до N-1). Сначала он ждёт, пока процесс j закончит выбор номера (если
choosing[j] == 1). Затем процесс ждёт, пока процесс j не станет «меньше» по правилу: номер j не равен 0 И (номер j меньше номера i ИЛИ (номера равны И j < i)). Это правило гарантирует, что процесс с меньшим номером (или с равным номером, но меньшим индексом) пройдёт в критическую секцию раньше. - Вход в критическую секцию: как только все процессы с меньшими номерами завершили работу, процесс входит в критическую секцию.
- Выход: процесс устанавливает свой номер в 0, сигнализируя, что он покинул критическую секцию.
Свойства алгоритма
Взаимное исключение
Алгоритм гарантирует, что в любой момент времени только один процесс может находиться в критической секции. Доказательство основано на том, что если два процесса A и B одновременно находятся в критической секции, то их номера должны быть равны, а индексы — различны, что противоречит правилу ожидания (один из них не смог бы пройти проверку).
Отсутствие взаимной блокировки (deadlock)
Алгоритм не допускает ситуаций, когда все процессы застревают в ожидании. Поскольку номера всегда возрастают, хотя бы один процесс (с наименьшим номером) сможет войти в критическую секцию.
Отсутствие голодания (starvation)
Каждый процесс, запросивший доступ, рано или поздно получит его. Справедливость обеспечивается тем, что номера назначаются в порядке возрастания, и процесс с меньшим номером всегда обслуживается раньше. Теоретически, если процессы постоянно добавляют новые номера, процесс с большим номером может ждать бесконечно, но на практике это маловероятно при конечном числе процессов.
Справедливость
Алгоритм является справедливым в том смысле, что процессы обслуживаются в порядке получения номеров (FIFO). Если процесс A получил номер раньше процесса B, то A войдёт в критическую секцию раньше B.
Ограничения и недостатки
Зависимость от общей памяти
Алгоритм требует, чтобы все процессы имели доступ к общей памяти для чтения и записи массивов choosing и number. В распределённых системах без общей памяти он неприменим.
Производительность
Алгоритм имеет сложность O(N) для каждого входа в критическую секцию, где N — число процессов. Это связано с необходимостью проверять все остальные процессы. При большом количестве процессов (например, сотни) производительность существенно падает.
Атомарность операций
Алгоритм предполагает, что операции чтения и записи целых чисел и булевых значений являются атомарными. В современных многоядерных системах с кэшами и переупорядочиванием инструкций это может не выполняться. Для корректной работы требуются барьеры памяти (memory barriers) или атомарные инструкции.
Риск переполнения
Номера могут неограниченно расти, что в конечном итоге может привести к переполнению целочисленного типа. На практике это редкость, так как процессы обычно не входят в критическую секцию бесконечно часто, но теоретически проблема существует.
Применение
Алгоритм пекарни в основном используется в учебных целях и в теоретических исследованиях в области параллельного программирования. Он демонстрирует, что взаимное исключение можно реализовать без специальных аппаратных инструкций, используя только обычные операции чтения и записи. В реальных операционных системах (Windows, Linux, macOS) для синхронизации применяются более эффективные примитивы: мьютексы, семафоры, спин-блокировки, реализованные с помощью атомарных инструкций процессора (например, xchg, cmpxchg, lock add). Однако алгоритм пекарни лёг в основу некоторых программных реализаций мьютексов для встраиваемых систем с ограниченными аппаратными возможностями.
Вариации и модификации
Существует несколько модификаций алгоритма, направленных на устранение его недостатков:
- Алгоритм пекарни с ограниченным числом номеров: для предотвращения переполнения номера могут циклически повторяться, но это требует дополнительных проверок.
- Алгоритм пекарни с использованием атомарного счётчика: вместо ручного вычисления максимума используется атомарная операция увеличения, что упрощает реализацию, но требует аппаратной поддержки.
- Алгоритм пекарни для распределённых систем: адаптации, использующие обмен сообщениями вместо общей памяти, но они теряют простоту оригинального решения.
Критика
Основная критика алгоритма связана с его практической неприменимостью в современных высокопроизводительных системах. Из-за необходимости проверять все процессы и отсутствия аппаратной поддержки атомарных операций над массивами, алгоритм значительно медленнее аппаратных спин-блокировок. Кроме того, он не масштабируется на большое число процессов. В современных учебных курсах по параллельному программированию алгоритм пекарни часто рассматривается как исторический пример, демонстрирующий принципы построения корректных параллельных алгоритмов, но не как практическое решение.
Источники
- Lamport L. A New Solution of Dijkstra’s Concurrent Programming Problem // Communications of the ACM. — 1974. — Vol. 17, No. 8. — P. 453–455.
- Таненбаум Э., Бос Х. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2015. — 1120 с.
- Silberschatz A., Galvin P. B., Gagne G. Operating System Concepts. — 10th ed. — Wiley, 2018. — 1040 p.
- Herlihy M., Shavit N. The Art of Multiprocessor Programming. — 2nd ed. — Morgan Kaufmann, 2020. — 560 p.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →