138
1. Перед выполнением обновления каждая транзакция
блокирует все объекты, которые она
собирается изменить. Если транзакции не удается установить необходимые блокировки, она
вообще не начинается.
2. Запрос на
блокировку выдается не вначале, а во время транзакции по мере выполнения
обновлений. Если необходимый объект уже заблокирован другим клиентом, то транзакция либо
ожидает его освобождения, либо завершается неудачей.
Вторая стратегия может привести к взаимной блокировке (deadlock): транзакция A блокирует объект X
и ожидает возможности блокировки объекта Y, тогда как транзакция B блокирует объект Y и ожидает
возможности блокировки объекта X.
В мире баз данных на эту тему написано огромное количество литературы,
поэтому нет смысла ее
здесь подробно рассматривать. Нас интересуют те идиомы и возможности С++, которые облегчают
решение целого класса подобных задач.
Отмена
Многие графические приложения любезно разрешают пользователю отменить последнюю
выполненную операцию. На первый взгляд в этом нет ничего сложного, но в действительности не все
так просто. В объектно-ориентированном мире найдется не
так уж много проблем дизайна, которые
вызывают большую головную боль, чем проблемы отмены в сложных приложениях.
Чтобы
реализовать отмену, многие программисты «зашивают» в программу структуры данных,
ориентированные на конкретную операцию, но такой подход чреват
ошибками и неустойчив при
сопровождении программы.
В некоторых языках (таких как Lisp) можно буквально делать «снимки» памяти в различные моменты
времени. При наличии такого снимка вернуться к прежнему состоянию приложения очень просто. Увы.
В С++ таких изящных возможностей не предусмотрено; зато
наши программы не запрашивают 3 Гб
памяти и успевают завершить работу еще до обеда. Так что это замечание стоит воспринимать не как
критику С++, а как констатацию факта: проблема есть, и ее необходимо решать.
В дальнейшем обсуждении будут рассматриваться две вариации на эту тему. Одни приложения всегда
предоставляют один уровень отмены, а в других пользователь может выбирать команду Undo снова и
снова — программа каждый раз возвращается на одну операцию назад. Конечно, ограничения все же
существуют, но обычно не очень жесткие. Это называется «многоуровневой отменой» — проблема,
которой мы займемся после одноуровневой отмены. Вторая вариация относится к контекстной отмене.
Если пользователь выполняет операцию отмены сначала в одном, а потом — в другом окне, то после
возврата к первому окну и выполнения команды Undo обычно следует отменить последнюю операцию
только для первого окна, а не для всего приложения.
Иначе говоря, операция отмены учитывает
контекст приложения на момент своего вызова.
Хватит?
Существуют и другие проблемы, относящиеся к тому же классу, но и сказанного вполне достаточно,
чтобы подтолкнуть нас к дальнейшим исследованиям. Большинство программистов рассматривает эти
проблемы как нечто изолированное в контексте конкретного приложения. Однако, как вы вскоре
убедитесь, некоторые специфические (а кто-то скажет — извращенные) особенности синтаксиса С++
позволяют приблизиться к общим решениям. Помните: мы изучаем С++ и его идиомы, а не структуры
данных или принципы проектирования программ. Многие архитектуры и
варианты здесь не
приводятся, однако это вовсе не говорит против них.
Образы и указатели
Разве можно начать новую главу и не ввести новую разновидность указателей? Состояние объекта
можно восстановить двумя способами: сохранить его образ (image) до и после операции или же
хранить
информацию, достаточную для выполнения операций в обратном направлении. Вариант
достаточно прост и универсален, а попытки возврата через выполнение
операций в обратном
направлении привязаны к конкретному объекту и приложению, поэтому я предпочитаю хранить
несколько копий одного объекта. Ключевая концепция, обеспечивающая реализацию этой методики на
С++ — указатель образов (image pointer). Такие указатели незаметно для клиента содержат несколько