Пейтон-Джонс: Да. Это действительно очень мощный принцип. Можно делать последовательные умозаключения относительно императивного кода, несмотря на параллелизм. Вы обязаны определить высокоуровневые инварианты, но это также полезно для душевного спокойствия: вы знаете, что именно пытаетесь сохранять. Если посреди транзакции встречается исключение, это тоже здорово — оно не может уничтожить инварианты, поскольку транзакция тогда завершается ничем. Просто сказка! И совершенно по-другому теперь можно рассуждать о скорости выполнения — вы удостоверились, что все минимально правильно, теперь надо убедиться, что программа нигде не подтормаживает. Это уже труднее: на сегодня есть только профилирующие инструменты и инструменты точечной обратной связи.
Сейбел: Меня поражает вот что: оптимистический параллелизм время от времени используется в персистентных базах данных, но намного реже по сравнению с параллелизмом на основе блокировки.
Пейтон-Джонс: Ну, транзакционную память можно реализовать несколькими способами, и оптимистический параллелизм — лишь один из них. Можно устраивать блокировку по мере продвижения — это уже больше похоже на пессимистический параллелизм.
Сейбел: Тут есть еще и тот момент, что менеджеры блокировок — самая сложная часть баз данных.
Пейтон-Джонс: Верно. Что касается транзакционной памяти, то нужна уверенность в том, что один человек — или команда — может ее реализовать, а остальные могут ею пользоваться. Чтобы иметь шанс убедиться в качестве работы, можно хорошо заплатить исполнителям и на год запереть их в темной комнатушке.
Но после этого у каждого должна быть возможность воспользоваться результатом его/их труда через простейший интерфейс. И вот это, по-моему, здорово. Мне бы не хотелось, чтобы каждому требовался подобный уровень знаний. Вот пример, который когда-то приводил Морис Херлихи, я только вчера на него ссылался: очередь с двухсторонним доступом, и вам нужно вставлять и удалять элементы.
Последовательная реализация очереди с двухсторонним доступом — это задача, которую проходят в объеме курса бакалавра. Для параллельной реализации с блокировкой по каждому узлу — это тема для научной статьи. Это слишком нелегко, прямо до абсурда. А для транзакционной памяти такую проблему решает студент. Вы просто «заворачиваете» операции «вставить» и «стереть» в одну атомарную операцию — и дело сделано. По-моему, это занятно. Тут есть количественная разница. Сегодня те, кто реализует транзакционную память, должны убедиться, что несколько изменений вносятся в память как единая атомарная операция. Это не так просто — у вас есть только атомарная инструкция «сравнение с обменом». Надо быть внимательным.
Есть проблемы и с зависанием; здесь надо кое-что придумать на уровне логики приложения, чтобы избежать этого. Но потом, реализуя специфичное для приложения решение, вы снова используете транзакционную память. Для такого вида программ это, безусловно, шаг вперед.
Хочу сказать еще кое о чем — мы опять возвращаемся к функциональному программированию. Транзакционная память, конечно, не имеет к нему прямого отношения. Она касается изменения разделяемого состояния — не самой функциональной вещи.
Дело в том, что когда-то я пошел на лекцию Тима Харриса о транзакционной памяти в Java. До этого о транзакционной памяти я вообще не слышал. Тим описывал «атомарные транзакции» и, в общем, больше ничего такого.
Я сказал: «Здорово, но тогда придется протоколировать все побочные эффекты в памяти, все инструкции по загрузке и хранению. А сколько их в Java!» Но в Haskell, благодаря монадам, их почти нет. Загрузка и сохранение в Haskell выражены явно, и программисты считают их важными операциями.
И я подумал, что надо ввести в Haskell все эти атомарные операции, — будет классно. Потом, после лекции, я стал объяснять Тиму, как это можно сделать. Вскоре, поскольку у нас была чистая, элегантная инфраструктура, мы придумали retry и orElse. Механизм retry позволяет осуществлять блокировку внутри транзакции, а orElse — выбор внутри транзакции. Тиму и его коллегам при разработке транзакционной памяти такое не пришло в голову, поскольку они имели дело с довольно сложной средой.
И потому они мало задумывались о блокировке — или предполагали, что блокировка будет атомарной операцией вида «запускать эту транзакцию только при наличии такого-то предиката». Но это очень некомпозиционно. Предположим, вы хотите переложить деньги с одного счета на другой: каковы условия проведения транзакции? Ответ: если на первом счете достаточно денег, а на другом достаточно места, — то есть имеются ограничения на обеих сторонах. Довольно сложное условие. Если задействован и третий счет, все еще сложнее. Все очень некомпозиционно — надо вытаскивать наружу все предусловия методов.