Недавно я задал несколько вопросов относительно TVar, и у меня все еще есть проблемы с livelock.
Итак, я подумал об этой структуре:
- Каждая транзакция получает уникальный приоритет (возможно, выделен в порядке создания).
- Транзакции пытаются получить блокировку чтения/записи для доступа к данным. Естественно, одновременные чтения в порядке, но одна блокировка записи исключает все остальные (как чтение, так и запись).
- Скажите, что транзакция A имеет более высокий приоритет, чем транзакция B. Если A удерживает блокировку, B ждет, но если B удерживает блокировку, а A хочет ее, B загружается из блокировки, A получает ее, а транзакция B перезапускается (например, с
TVar). Однако B сохраняет свой текущий приоритет для повтора. - Когда блокировка освобождается и ожидаются транзакции, она переходит на транзакцию с самым высоким приоритетом, а остальные продолжают ждать.
Эта система, я считаю, предотвращает взаимоблокировки, но также предотвращает голодание (в отличие от TVar). Мне было интересно, если кто-то реализовал такую систему, как это кажется довольно очевидным, и я не хочу изобретать велосипед.
Конечно, такой подход может быть легко расширен, чтобы пользователь мог определять приоритеты.
Приоритетом может быть пара (user_supplied_prio, auto_increment), с приоритетом user_supplied_prio, но равные приоритетные задачи, разрешающие в порядке FIFO.
Комментарий/Решение:
Собственно, когда я думаю об этом, то, что я описываю, уже существует в Haskell, просто используя один IORef, обернутый вокруг всех данных, и используя только atomicModifyIORef. atomicModifyIORef гарантирует, что транзакции будут применяться последовательно. Однако можно подумать, что это означает, что структура данных является последовательной (то есть эффективно ограничена одним потоком), но она фактически параллельна из-за лени.
Чтобы объяснить это, рассмотрим дорогостоящую функцию f. Мы собираемся применить это к Data.Map к данным с помощью ключа "foo".
Это заменяет (foo -> x) на (foo -> future(f x)). Этот поток должен продолжить работу над тем, что (f x) на самом деле, но в то же время мы можем применить g к "bar". Так как применение g к "bar" не нуждается в результате "foo", мы можем работать это одновременно.
Без взаимоблокировок, без голода, в конечном итоге все транзакции будут обработаны (примерно в том порядке, в котором они получены).