Мне кажется, что все эти семафоры (из устаревших подходов), транзакционная память (из более новых, в mainstream-программировании) - имеют под собой одну нехорошую черту. Все это - пережитки тех мастеров жанра, кто до сих пор мыслит в... функционально-процедурном стиле программирования...
Давайте раскатаем их "в губу" и покажем преимущества ООП-подхода.
Задача 1.
1 Есть объект Поставщик и есть объект Потребитель.
2 В каждом из них - есть по одному полю данных (любой объект может состоять из двух вещей - данных и методов работы с этими данными).
3 Требуется организовать обмен данными между этими двумя объектами. Проще говоря - когда у Поставщика его поле становится равным 1 (т.е. у него что-то есть) - это значение нужно "перебросить" Потребителю. При этом, после "переброски" - у Поставщика значение этого поля становится равным 0.
4 При этом Потребитель не должен ждать, он должен "что-то свое делать".
Решения:
1.1 Когда у Поставщика появляются данные - он "шлет" сообщение Потребителю. Потребитель забирает данные, обнуляет соотв. поле у Поставщика. Паттерн "Observer" ("Наблюдатель"). Все. Все, задача уже решена.
1.2. То же самое решение, что и 1.1, отличие только в том, что используем, в добавок, паттерн "Command". Чтобы инкапсулировать передачу данных (тогда можем "прямо и пересылать, сколько нужно, какую-то величину, например"). Можно еще использовать, при этом, паттерн "Object Value" (Фаулер пишет, что по поводу "Object Value" - могут возникать разночтения - есть два разных паттерна, названных так).
Задача 2.
Та же самая задача, что и задача 1, отличие только в том, что, пускай еще будет 1 объект Поставщик. Итого - 2 объекта класса Поставщика и 1 объект класса Потребителя. Потребитель должен "забирать бабло" у обоих Поставщиков.
Решения:
Те же самые, что и к задаче 1.
Задача 3.
Та же самая задача, что и задача 2, отличие только в том, что есть еще один объект Потребителя. Итого - 2 объекта класса Поставщик и 2 объекта класса Потребитель. Ну и есть какое-то правило - как, когда и сколько - должны забирать данные объекты Потребители.
Решения:
Те же самые, что и к задаче 1 + нужно добавить диспетчеры, координаторы или еще что хотите - на любой вкус и цвет. Здесь нужно отметить, что есть ОЧЕНЬ много паттернов проектирования, относящиеся к системам, основанным на обмене сообщениями. Да хоть очереди сообщений тут внедряйте, да хоть - пускайте тестовые сообщения, - к ним еще дроссели прикрутите к системе, можете добавить пулы, можете добавить балансировку нагрузки - ну о-о-очень много вариантов реализации, на любой вкус и цвет...
Слышу голоса -- А где же потоки??? - Ладно, будут вам и потоки...
Задача 4.
Есть объект Склад. На который "складирует" объект Поставщик (выполняющийся в своем потоке) и с которого забирает данные объект Потребитель (выполняющийся в другом потоке).
Решения:
Те же самые, что и к задаче 3.
Задача 5.
То же самое, что и задача 4. Отличие только в том, что у всех-всех-всех объектов - есть по 10 полей данных (мука, кукуруза, сгущенка, хлеб и т.д.) Требуются какие-то скоординированно-согласованные действия.
Решения:
А как же в жизни то бывает? - Есть некий нач. склада. Который принимает квитанции о приходе, принимает квитанции об убытии. И "рулит" все правила. - Кому, сколько, когда. Другими словами - нужна целая подсистема, которая будет рассылать события и в которой и будут "прописаны" все правила.
А теперь - типичная "функционально-процедурная постановка" задачи про "сложности" с многопоточностью:
"Рассмотрим, например, класс, реализующий набор банковских счетов. В этом классе имеются две безопасные для потоков управления операции
Deposit
и Withdraw
, предназначенные для зачисления денег на счет и снятия их со счета соответственно. Предположим, что требуется произвести композицию этих операций в безопасную для потоков операцию Transfer
, которая переводит деньги с одного счета на другой. Промежуточное состояние, когда деньги сняты с одного счета и не зачислены на другой счет, не должно быть видимым для других потоков управления (т.е. перевод должен быть атомарным). Поскольку в операциях Deposit
и Withdraw
счет блокируется только на время их выполнения, для правильной реализации операции Transfer
требуется понять дисциплину блокировок, используемую в данном классе, и изменить ее путем добавления метода для блокировки всех счетов или какого-нибудь одного счета. Последний подход позволяет выполнять параллельно операции переводов с разными счетами, но при этом появляется возможность синхронизационного тупика, если выполнение операции перевода со счета A
на счет B
перекрывается во времени с выполнением операции перевода со счета B
на счет A
. "- Другими словами, авторы примера - объединяют 2 атомарные операции - в одну (снятие денег с одного счета + зачисление денег на другой счет = перевод денег), при этом, само собой - сложность увеличивается (в противоположность декомпозиции сложности, как методу решения задач). Само собой, если думать в 3D-измерении (а не "размазывать" все в 2D-измерении), то нужен какой-то НАДО ВСЕМ ЭТИМ - управляющий центр. У которого должна быть система правил. Авторы про это и намекают, вернее - задаются мыслью ("требуется понять дисциплину блокировок ... и изменить ее"), что чего б такого придумать, в данной ситуации.
Управляющий центр операции перевода денег. Центр. Объект. Координатор. В который закладываем систему правил. Возможно и как "конечный автомат".
Центру поступила команда -- "Перевести с одного банка 100 долларов на другой, но убедиться, и соблюсти. Что деньги перечислились. После этого уже - инкремировать один счет и декримировать - другой счет".
Ну и что может быть проще?
Центру поступил event (событие). Центр послал event одному банку, с номером ТЕКУЩЕГО трансфера и Центр послал, одновременно - второй event, второму банку, с номером все того же ТЕКУЩЕГО трансфера. Один банк прислал ответ - "Деньги получены!" - Хорошо, ОК, теперь Центр шлет обоим банкам еще по одному событию, чтобы реально уже списали/зачислили.
На деле механизм событий может быть оптимизирован ("размазан в 2D") - если точно известно, что эта операция - высоконагруженная, тогда чтобы симитировать события - можно воспользоваться несколькими регистрами. Если они принимают какие-то значения, значит "что-то состоялось". Если они в состоянии "не определены", значит операция еще "в процессе". При чем, как мне кажется - можно использовать именно группы таких регистров (переменных). - Все вместе они будут характеризовать состояние операции (грубо говоря - это что-то вроде мини-СУБД, к которой может обращаться Центр и "считать себе", анализировать, что ему надо).
А знаете как на деле происходит, если, например, вы расплачиваетесь кредиткой Visa Electron от Альфа-банка (не забыть сгрести от них бабло, за рекламу) - чтобы купить что-то в Интернете на одном из аукционов или просто какую-то книжку в Интернет-магазине?
- А происходит вот что - вы кликнули на сайте на кнопку покупки, далее Альфа-банк замораживает у себя сумму, эквивиалентную сумме вашей покупки. После этого банк и Интернет-магазин - производят свою банковскую операцию. Если все удачно, то у вас эту, ранее замороженную сумму - списывают. Все, ее уже нет. Если же что-то "пошло не так" - трансферинг денег между этими платежными системами - будет отменен. И вашу эту сумму - разморозят. А замораживали ее для того, чтобы вы не накликали на миллион долларов. И потому что операция перечисления денег - не мгновенная (это в теории только - два байта переслать, на деле нужны еще всякие подтверждения).
- Так вот. Бизнес-требования банка, к данной операции - известны. В данном случае управляющий объект, Центр координации - расположен в банке. Это его правила - замораживать сумму, или нет. Вы только, образно говоря - запустили всю программу на выполнение (метод main()).
Какой-то банк - еще и письменное подтверждение ждать будет. Или если Вы перечисляете 1.000.000 долларов - то Вашего личного присутствия будет ждать. И подписи. А не просто "клик-клик" по Инету. А могут быть еще и всякие федеральные органы, регулирующие права "хозяйствующих субъектов". Банк еще может и аудиторам свой отчет слать. Ну и т.д.
Если у вас что-то не совсем сложилось в голове - как это все запрограммировать - то спрашивайте, не стесняйтесь. Лично мне так сразу понятно, как и что...
UPD 2. Вышло продолжение, 2-ая часть статьи.
0 коммент.:
Отправить комментарий