В этом задании вы реализуете эффективный алгоритм Обедающих Философов для взаимного исключения в распределенной системе.
В файле src/Process.kt
находится описание интерфейса, который вам предстоит реализовать.
Свой код вы должны писать на языке Kotlin в файле src/ProcessImpl.kt
.
Не забудьте указать свое имя и фамилию в файле с вашим решением.
Допустима также реализация на Java. Для этого удалите ProcessImpl.kt
и вместо него напишите файл
ProcessImpl.java
с классом mutex.ProcessImpl
, который реализует интерфейс mutex.Process
и имеет
публичный конструктор, принимающий ссылку на объект реализующий интерфейс mutex.Environment
.
Шаблон для такого класса дан в файле src/ProcessJavaImpl.java
.
Для работы над проектом рекомендуется импортировать Gradle проект
build.gradle.kts
в IntelliJ IDEA, но это не обязательно. Работу и тестирование
проекта можно также производить из командной строки.
Тестовая система будет запускать ваш код в нескольких процессах, каждому из которых выдан уникальный
идентификатор начинающийся с единицы. Через ссылку на объект Environment
ваш процесс
может узнать конфигурацию системы и общаться с другими процессами:
env.processId
— возвращается идентификатор вашего процесса (нумерация с единицы).env.nProcesses
— возвращает общее число процессов.env.send(destId, message)
— посылает сообщение процессу с номеромdestId
(нумерация с единицы).env.locked()
— должен быть вызвать вашим процессом при входе в критическую секцию только если был запрос (смотри ниже).env.unlocked()
— должен быть вызван вашим процессом при выходе из критической секции только если был запрос (смотри ниже).
Методы вашего класса ProcessImpl.kt
будут вызываться из главного потока процесса в следующих случаях:
onMessage(srcId, message)
— вызывается при получении сообщения от другого процесса с номермsrcId
(нумерация с единицы), которое было послано другим процессом черезenv.send(...)
. Между каждой парой процессов гарантируется FIFO порядок передачи сообщений (передача происходит через протокол TCP/IP).onLockRequest()
— вызывается в случае запроса на вход в критическую секцию. Процесс должен инициировать алгоритм входа в критическую секцию и, после входа в неё, вызватьenv.locked
. Этот метод не будет повторно вызыватьcя до выхода из критической секции.onUnlockRequest()
— вызывается в случае запроса на выход из критической секции. Процесс должен вызватьenv.unlocked
и инициировать алгоритм выхода из критической секции. Этот метод будет вызываться только если ранее был осуществлен вход в критическую секцию (был вызванenv.locked
).
Сообщения формируются путем создания объекта класса Message
. Каждое сообщение
состоит из последовательности полей одного из трех типов: числового, строкового, перечислимого. Запись
полей в сообщение осуществляется с помощью вызовов сооствествующих методов writeXxx
, а чтение — с помощью
соответсвтующей последовательности вызовов readXxx
. Например, чтобы создать сообщение состоящие из
из числового поля time
и перечисления типа:
enum class Type { REQ, OK }
Надо написать такой код:
val message = Message {
writeInt(time)
writeEnum(Type.REQ)
}
А для того, чтобы такое сообщение прочитать:
message.parse {
val time = readInt()
val type = readEnum<Type>()
// ... use type & time here
}
Попытка делать при чтении другие вызовы, не соотвествующие вызовам используемым при создании сообщения, приведет к ошибке.
Для удобства можно объединить создание сообщения и его посылку в один вызов:
evn.send(destId) {
writeInt(time)
writeEnum(Type.REQ)
}
К заданию прилагается ряд уже реализованных алгоритмов взаимного исключения. Они не подходят в качестве ответа на данное здание, но помогут вам разобраться с окружением и способами реализации различных распределенных алгоритмов, а также прояснить требования предъявляемые к алгоритмам взаимного исключения в данном задании.
ProcessLamportMutex
— алгоритм взамного исключения Лампорта, посылает3*(N - 1)
сообщений на каждую критическую секцию.ProcessRickartAgrawalaMutex
— алгоритм взамного исключения Рикарта и Агравалы, посылает2*(N - 1)
соообщений на каждую критическую секцию.ProcessSyncCoordinatedMutex
— централизованный алгоритм взамного исключения (процесс 1 является координатором), посылает2
сообщения на каждую критическую секцию, а координатор входит в критеческую секцию не посылая сообщений.ProcessTokenMutex
— алгоритм взаимного исключения на основе токена. Он постоянно передает сообщение по кругу, даже если нет запросов на критическую секцию. В данной реализации, для удобства отладки, в передоваем сообщении содержится постоянно увеличивающийся идентификатор, который не нужен для работы самого алгоритма.
Для запуска и отладки кода предлагается ряд готовых инструментов.
Конфигурация запускаемой распределенной системы находится в файле system.properties
и состоит
из перечисления процессов (узлов) системы и их адресов. По умолчанию система сконфигурирована для работы с 5-ю процессами, запускаемыми
на одной машине. Для отладки своего кода эта конфигурация может быть изменена.
Запусть процесс можно одим из двух способов:
- Запустив
main
функцию в файлеsrc/system/Node.kt
, передав ей в качестве аргумента номер процесса. - Из командной строки
gradlew node -PprocessId=<id>
.
На экране будет виден подробный журнал работы просесса. Все посылаемые и принимаемые сообщения и всё взаимодействие с окружением всегда выводятся системой в журнал работы (для этого к коде процесса не надо писать никакого специального отладочного кода). В консоли можно вводить следующие команды:
exit
— останавливает работу.ping
— проверяет жив ли процесс (отвечает на консоли сообщениемPONG
).lock
— запрос на критическую секцию.unlock
— запрос на выход из критической секции.
Запусть все процессы одновременно можно одим из двух способов:
- Запустив
main
функцию в файлеsrc/system/System.kt
. - Из командной строки
gradlew system
.
На экране будет виден подробный журнал работы всех процессов. В консоли можно вводить следующие команды:
exit
— останавливает работу все процессов.ping
— проверяет живы ли процессы (посылаетping
всем процессам).lock
— запрос на критическую секцию всем процессам (посылаетlock
всем процессам). В правильно работающем алгоритме только один процесс войдет в критическую секцию (в журнале появится запись<id> LOCKED
) и будет дожидаться запроса на выход из критической секции.<id> lock
— запрос на критическую секцию к процессу с номером<id>
.<id> unlock
— запрос на выход из критической секции к процессу с номером<id>
.
По умолчанию, происходит запус процесcа реализованного в классе ProcessImpl
. Не копируя исходный код,
можно сразу запустить одну из готовых реализаций, перечисленных в секции Примеры алгоритмов,
передав имя класса в качестве дополнительного аргурмента при запуске соответствующей main
функции, например:
SystemKt ProcessLamportMutex
При запуске из командной строки надо указывать свойство проекта implName
, например:
gradlew system -PimplName=ProcessLamportMutex
Тестирования реализации происходит путем запуска теста MutualExclusionTest
.
Из командной строки: gradlew test
.
Тест проверяет корретность алгоритма (гарантию взаминого исключения), его прогресса (отсутствие взаимных блокировок), и замеряет эффективность алгоритма, считая количество передаваемых сообщений. Из-за проверки на эффективность, прилагаемые к заданию примеры алгоритмов не проходят тест.
Можно запускать тест в режиме, когда проверяется только взаимное исключение, но не эффективность алгоритма,
передавая свойство проекта skipCountCheck=true
.
При такой проверке прилагаемые Примеры алгоритмов проходят тест, например:
gradlew test -PimplName=ProcessRickartAgrawalaMutex -PskipCountCheck=true
Для прохождения полноценного теста и сдачи задания необходимо реализовать алгоритм Обедающих Философов.
Выполняйте задание в этом репозитории.
Ваш код должен быть реализован в одном файле src/ProcessImpl.kt
или src/ProcessImpl.java
.
Инструкции по сдаче заданий находятся в этом документе.