Анимация
JavaScript
|
Главная Библионтека отказываться от второй транзакции, обычно стоит подождать снятия блокировки с объекта текущей транзакцией. При отсутствии очередей вам не придется беспокоиться о ситуациях взаимной блокировки (когда A ожидает B, а B ожидает A). Если транзакция запросила блокировку и не смогла ее получить, она уничтожается. Если очереди поддерживаются, код блокировки должен определить, принадлежит ли этой транзакции какие-либо блокировки, которых дожидаются остальные, и если принадлежат, избавить одну из транзакций от бескенечного ожидания. Происходящее оказывается в опасной близости от точки, в которой вам приходится либо разбивать свое приложение на несколько подзадач и поручать операционной системе их планирование, либо воспользоваться одной из коммерческих библиотек с поддержкой многопоточности. Так или иначе, в этой области мы не узнаем ничего принципиально нового, относящегося к С++, и поэтому не будем развивать эту тему. Об очередях, блокировках и многопоточности написано немало хороших книг, вы наверняка найдете их в разделе «Базы данных» своего книжного магазина. Многоуровневая отмена Семантика транзакций довольно легко распространяется и на многоуровневую отмену (если вспомнить концепции StackPtr, о которых говорилось выше). Существует два основных варианта реализации. Класс LockPtr со стеками образов Самый прямолинейный вариант реализации многоуровневой отмены для транзакций - включение стека старых образов в каждый LockPtr. Эта идея позаимствована из рассмотренного выше кода StackPtr. Тем не менее, она подходит лишь для консервативной блокировки. В случае с агрессивной блокировкой объект может быть впервые заблокирован уже после изменения остальных объектов. Это усложняет отмену нескольких изменений, поскольку стеки разных LockPtr не синхронизируются. Стеки пар LockPtr/образ К проблеме можно подойти и иначе - включить в Transaction стек, в котором хранятся пары старый образ/LockPtr. На каждом уровне стека хранятся лишь те LockPtr, которые существовали на момент закрепления. В общем случае это решение работает лучше, к тому же оно чуть более эффективно - вы используете один большой стек вместо множества маленьких. Оптимизация объема Другое решение - реорганизация структур данных для сокращения издержек, связанных с хранением незаблокированных ConstPtr (хотя и ценой некоторой потери скорости). ConstPtr лишь незначительно отличается от указателей только для чтения, которые рассматривались в главе 6 и не имели ничего общего с транзакциями: он имеет ссылку на ConstPtr и функцию Lock(). Мы избавимся от первого и внесем некоторые изменения во второе. Представьте себе глобальную структуру данных (вероятно, хранящуюся в виде статического члена класса Transaction), в которой находится информация о том, какие Transaction блокируют какие ConstPtr. Для каждой пары в таблице содержится соответствующий LockPtr. Каждый раз, когда вызывается функция Lock() класса ConstPtr, она проверяет, присутствует ли this в таблице. Если присутствует, функция сравнивает транзакцию, переданную в качестве аргумента, с находящейся в таблице. Если ConstPtr не находит себя в таблице, он включает в нее новый триплет (ConstPtr, Transaction, LockPtr), а если находит с другой транзакцией - инициирует исключение. Такая схема оказывается более экономной, чем описанная выше; она не тратит память на значения NULL для всех незаблокированных объектов. Разумеется, она сложенее и медленнее работает - структура данных еще только разогревает двигатель на старте, а косвенной обращение через переменную класса уже пересекает финишную черту. Возможно, у вас возник вопрос - а почему функция Lock() должна оставаться с ConstPtr? Почему ее нельзя включить в другой класс или даже сделать глобальной функцией? Если мы избавимся от переменной LockPtr* и функции Lock(), ConstPtr превратится в самый обычный указатель только для чтения, который на вопрос о транзакциях лишь тупо смотрит на вопрошающего. Впрочем, так ли это? LockPtr по-прежнему приходится объявлять другом; следовательно, хотя бы тривиальных изменений не избежать. Более того, Transaction не знает конкретный тип указываемого объекта и потому не может использовать шаблон LockPtr для создания объекта блокировки. Если вы еще не забыли, абстрактный базовый класс Lock создавался именно для этой цели. Увы. Нам хотелось бы оставить ConstPtr в счастливом неведении но не суждено. Несколько прощальных слов Реализация полной схемы обработки транзакций занимает не так уж много места, но от этого она не становится проще. Изменения приходится вносить практически в любой класс, который ссылается на другие классы, поскольку большинство указателей должно соответствовать соглашениям ConstPtr/LockPtr. Впрочем, это не демонстрирует полную безнадежность подобных попыток на С++, а лишь подчеркивает важность соблюдения двух основных принципов: 1 . Используйте умные указатели, даже если вы не уверены, что это нужно. 2. Фанатично следите за тем, чтобы константные обращения отделялись от неконстантных. Если ваш код будет изначально устроен таким образом, подобная расширенная архитектура реализуется на порядок проще. В этом кроется один из мистических принципов С++, о которых говорилось выше - вы должны понять не то, как язык справляется с конкретной проблемой, а то, как он обеспечивает построение полноценной, надежной программы. Некоторые инструменты С++, поддерживающие подобную архитектуру, не имеют аналогов в других языках (взять хотя бы конструкторы копий, операторы = и -> ). Благодаря этим синтаксическим и семантическим странностям сложные библиотеки классов становятся более понятными и полезными. Только подумайте, насколько бы все усложнилось, если бы мы не могли перегрузить оператор -> в ConstPtr и LockPtr. При правильном выборе идиом С++ не подведет ни на скользкой дороге, ни в плохую погоду. Наконец, помните о том, что эта глава предназначалась для тренировки творческого воображения - мы хотели узнать, куда нас заведет концепция умного указателя, если применить ее на свежем материале. Ответ: довольно далеко. После добавления некоторых идиом и принципов дизайна, рассмотренных в следующих главах, подобную архитектуру будет легче воплотить в реальной программе. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [ 47 ] 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |