вторник, 27 ноября 2007 г.

Новая версия библиотеки act-o

После достаточно продолжительного перерыва, я рад сообщить, что выложил новую версию библиотеки act-o.

Надо сказать, что до этого на sourceforge.net находилась концептуальная версия библиотеки – у ней был проработан интерфейс, можно было описывать взаимодействие актеров, можно было запускать программы на ее основе, но библиотека не могла продолжительное время работать под нагрузкой. Данный же релиз обладает полноценной средой выполнения (runtime) и планировщиком потоков.

Особо важным считаю тот факт, что в то время как среда выполнения была почти полностью переработана, интерфейс библиотеки остался неизменным. Это означает, что летний – проектный этап был успешным. Хотя некоторые косметические изменения все же пришлось внести.
  • Изменился заголовочный файл, который необходимо подключать, чтобы воспользоваться библиотекой – вместо «act_o.h» теперь необходимо подключать «multiprog.h».
  • Переименовано пространство имен – вместо act_o теперь необходимо писать acto (без подчеркивания).
  • Были добавлены две обязательные функции, которые необходимо явно вызывать acto::startup – инициализация библиотеки и acto::shutdown – закрытие библиотеки и освобождение всех ресурсов.
Возможности среды выполнения

Новая реализация runtime’а обладает следующими характеристиками:
  • Позволяет указывать объекты, сообщения для которых будут обрабатываться в отдельном потоке (эксклюзивные объекты) и объекты, потоки под которые будут распределяться автоматически (управляемы объект).
  • Подстраивается под загруженность системы и может выделять дополнительные потоки под управляемые объекты, если текущие потоки не справляются с обработкой всех сообщений. Это позволяет продолжать обработку сообщений, даже если часть объектов выполняет сверхдолгие или блокирующие операции. В данной версии установлено ограничение на максимальное число потоков – 512.
  • Автоматически удаляет излишние потоки, выделенные под управляемые объекты, при уменьшении нагрузки на систему.
  • Использует подсчет ссылок для определения того момента, когда объект можно удалять, но так как большинство объектов имеют ссылки на самого себя, то необходимо явно указывать момент удаления объекта с помощью функции acto::destroy. Однако следует отметить, что такой механизм не порождает проблем с повисшими ссылками, так как я позаботился о том, чтобы заголовок объекта удалялся только тогда, когда на него больше не остается ссылок. Некоторые причины такого способа управления объектов я описал ранее в своей заметке «Необходимость сборки мусора в многопоточных программах».
Тест работоспособности

Для того чтобы убедиться, что среда выполнения работает исправно, я провел тест на основе программы «ping pong», которая поставляется вместе с библиотекой. Вы также можете запустить ее у себя и сравнить результаты.

Тестирование проводилось со следующими параметрами: 50000 активных объектов, 50000 одновременно пересылаемых сообщений, 50 циклов start/stop в каждом из которых по 50 циклов активного обмена сообщениями. Код, который выполнялся в данном тесте, можно посмотреть, загрузив пакет библиотеки act-o, в файле «samples/ping-pong/main.cpp».

Тестовая платформа: ASUS P5K-VM, Intel Core Duo E2180, 2GB – DIMM PC-5300 DDR II, Windows XP x64 Pro.

Были получены следующие результаты – на ненагруженной машине средний показатель производительности составил 400’000 сообщений/сек. При тестировании было замечено, что среда выполнения не очень устойчива к сверхбольшой нагрузке – больше 100’000 активных объектов и больше 100’000 одновременно пересылаемых сообщений. Конечно, программа не падает, но работает очень медленно и в заданный интервал времени не успевает обработать все сообщения. Я думаю, что это в основном связано с отсутствием специально адаптированного менеджера памяти. Также в текущем алгоритме планирования есть вероятность избыточного расходования системных потоков – данный эффект может проявляться при очень плотном потоке сообщений.

Использование и дальнейшее развитие

На данный момент я применяю библиотеку act-o для создания серверных приложений. Если быть точнее, то для программирования асинхронной работы внутри сетевых серверов. Это позволяет мне легко организовать асинхронное взаимодействие между объектами приложения в случае использования неблокирующих сокетов.

Библиотека сетевых компонентов находится на ранней стадии производства и еще сложно сказать насколько может различаться производительность приложений сделанных на основе act-o и приложений, где многопоточность реализуется «руками». Пока что могу выразить только свое субъективное впечатление по поводу упрощения организации асинхронного взаимодействия между объектами и избавления от проблем с разделяемой памятью – ну почти как в Erlange… ;-)

Касательно планов дальнейшего совершенствования библиотеки act-o, то в первую очередь, я хочу снабдить ее полноценным многопоточным менеджером памяти, и добавить некоторые дополнительные возможности. Некоторые из них уже разработаны – например, таймер, который может посылать другим пользовательским объектам сообщение msg_time через указанные промежутки времени. В данный же релиз библиотеки он не включен потому, что его интерфейс еще недостаточно продуман и могут быть внесены изменения, которые сделают его несовместимыми с предыдущими версиями.

В более отдаленных планах еще раз переписать runtime уже на основе более проработанной модели планирования обработки сообщений и, возможно, снабдить его алгоритмом сборки мусора. Как и обычно менять интерфейс библиотеки при этом не планируется. Только возможно, что удастся избавиться от явного удаление объектов функцией acto::destroy, или как минимум сделать ее необязательной.

Вообщем, буду рад узнать, если библиотека act-o поможет вам при разработке ваших приложений. Со мной всегда можно связаться через этот блог, или написать прямо по почте – адреса можно найти в профиле или в заголовках к коду библиотеки.

вторник, 4 сентября 2007 г.

Рандеву – концепция параллельного программирования

Изучая концепции параллельного программирования, приходится часто сталкиваться с такой концепцией как «рандеву». Многие упоминают ее, говорят, что на основе этой концепции построена модель параллелизма в языке Ada, но как-то мне не довилось еще встретить материал, в котором рассказывалось бы о сути рандеву. Поэтому я решил восполнить пробел, в первую очередь в своих знаниях, но надеюсь, что вам это тоже будет интересно и полезно.

Коль скоро рандеву – это основная концепция организации межпроцессного взаимодействия в языке Ada, то в поисках ответ о том, что такое рандеву, я решил обратиться, что называется, к первоисточникам. В ru-нете есть сайт по языку Ada, где я обнаружил книгу, в которой излагается суть механизма рандеву и принципы его описания в языке. Книга называется «Адское программирование» и в «главе 15: Многозадачность» описывается идея рандеву. Процитирую ту часть, в которой описывается суть термина, а за подробностями и разъяснениями предлагаю обратиться к самой книге.

Как один из механизмов обеспечения надежного межзадачного обмена данными и взаимной синхронизации работы задач, Ада предоставляет механизм рандеву.
Основополагающая идея механизма рандеву достаточно проста. В спецификации задачи публикуются различные входы (entry) в задачу, в которых она готова ожидать обращения к ней от других задач. Далее, в теле задачи указываются инструкции принятия обращений к соответствующим входам, указанным в спецификации этой задачи.

Необходимо обратить внимание на несимметричность такого механизма взаимодействия. Это означает, что в процессе взаимодействия одна из задач рассматривается как сервер, а вторая - как клиент, причем задача-сервер не может быть инициатором начала взаимодействия.

В простейшем случае, когда рассматривается взаимодействие только двух задач, задача-клиент, желающая обратиться к другой задаче (задаче-серверу), инициирует обращение к входу задачи-сервера. После этого задача-сервер откликается на обращение задачи-клиента, принимая обращение к этому входу. Таким образом, взаимодействие двух задач осуществляется в ситуации, когда задача-клиент обратилась к входу, а задача-сервер готова принять это обращение. Этот способ взаимодействия двух задач называется рандеву.

Поскольку задача-клиент и задача-сервер выполняются независимо друг от друга, то нет никакой гарантии, что обе задачи окажутся в точке осуществления рандеву одновременно. Поэтому, если задача-сервер оказалась в точке рандеву, но при этом нет ни одного обращения к входу (запроса на взаимодействие), то она должна ждать появления такого обращения. Аналогично, если задача-клиент обращается к входу, а задача-сервер не готова обслужить такое обращение, то задача-клиент должна ждать, пока задача-сервер обслужит это обращение. В процессе ожидания, как задача-клиент, так и задача-сервер не занимают ресурсы процессора, находясь в состоянии, которое называют приостановленным или состоянием блокировки.


Если внимательно почитать вышеуказанную книгу, то можно узнать, что рандеву, как и модель актеров, предлагает изолировать состояния процесса (объекта). И изменять его только в специальных точках взаимодействия. Только рандеву осуществляет синхронный механизм взаимодействия, в то время как сообщения – предоставляют асинхронный механизм.

В следующий раз расскажу о том, как я, пытаясь реализовать взаимодействие между потоками в ядре библиотеки act-o, естественным путем пришел к точно такой же идее взаимодействия как рандеву, еще до того, как познакомился с вышеизложенным материалом.
Оставайтесь на связи… :-)

воскресенье, 2 сентября 2007 г.

И все-таки она необходима

Стоило только мне вынести предыдущую заметку на один форум, как оказалось, что:
  1. Сборка мусора необходима не только в многопоточных программах, но и в компонентном программировании.
  2. Существует статья, в которой предпринята попытка доказать необходимость автоматического управления памятью в компонентных программах.
Конечно, это вопрос еще предстоит проработать более подробно применительно именно к многопоточному программированию, но думаю, что общий ответ явно не вызывает сомнения – автоматическое управление памятью (сборка мусора) необходимо в системах с массовым параллелизмом.

четверг, 23 августа 2007 г.

Необходимость «сборки мусора» в многопоточных программах

Те из вас, кто следит за событиями в мире языков программирования, наверное, неоднократно сталкивались с заявлениями, что мы стоим на пороге новой революции, и имя этой революции – параллельность. Началась она несколько лет назад, но сейчас, когда 2-х и 4-х ядерные процессоры доступны любому в ближайшем магазине, она стала актуальна. И дело конечно не в том, что такие процессоры существуют, а в том, что, если мы хотим и дальше получать все большую производительность от наших компьютеров, нам необходимо использовать эти ядра, так как мощность одного ядра достигла некоторого придела. Тем же, кто еще только начинает знакомиться с этой темой, я бы предложил начать с хорошей и очень известной статьи Герба Шаттера (Herb Sutter) «The Free Lunch Is Over».

Многоядерные процессоры стали реальностью, и сбежать от них нам не удастся, но легко сказать: «Необходимо использовать имеющиеся ядра», – гораздо сложнее сказать, как именно это сделать. Большинство сходятся во мнении, что для этого необходим новый язык программирования. Возможно, что такой язык уже есть, возможно, его еще предстоит создать, но одно известно точно – в коммерческом программировании (mainstream) его еще не используют. Не буду сейчас пытаться анализировать этот язык вообще, а акцентирую внимание только на одной характеристики нового языка, без которой, на мой взгляд, невозможно качественное создание программ с множественным параллелизмом.

Разрабатывая библиотеку act-o и ее продолжение, я все время сталкиваюсь с ситуацией, когда мне необходимо удалить объект, но в многопоточном приложении неизвестно работают ли другие потоки с этим объектом, или с объектами, на которые он ссылается, или которые ссылаются на него. В текущей публичной версии библиотеки act-o есть такой пример:

// Создать актера
act_o::actor_t wall = act_o::instance_t <> ( act_o::aoExclusive );
// Начать игру: инициализировать объект, запустить мячи
wall.send( msg_start( BALLS, console ) );
// Остановить игру
wall.send( msg_finish() );
// Уничтожить объект
act_o::destroy( wall );

В первой строке кода происходит создание объекта класса Wall, который является актером. Однако в программе программист оперирует не указателями на объект класса Wall, а специальным объектом-оберткой класса act_o::actor_t (в контексте данной заметки я буду называть такой объект «ссылкой»). Объект-ссылка сделан для того, чтобы управлять указателем на объект класса Wall (в данном случае).

Зачем мне понадобилось использовать объекты, управляющие указателями? Помимо очевидной защиты от утечки памяти, есть и более серьезное основание для этого. Связано оно с тем, что в программе можно не только оперировать ссылкой на актера, созданного в текущем блоке, но и передать ее другому актеру в качестве параметра сообщения. Я уже писал об этом в своей предыдущей заметке «О моделях актеров», где излагал суть модели:

Получатель сообщения определяется только по адресу. Иногда такой адрес называется «почтовый адрес». Таким образом, актер может взаимодействовать только с теми актерами, адреса которых ему известны. Сам же актер может получить адреса других только из пришедших сообщений, также он по определению знает адреса тех актеров, которые были созданы им самим.

В данном случае адреса – это ссылки, которые в библиотеке act-o представлены классом act_o::actor_t. В приведенной цитате видно, что ссылки на данного актера теоретически могут находиться где угодно: у других актеров, в очереди сообщений. И возникает вопрос: можно ли в асинхронной многопоточной модели синхронизировать состояние всех объектов так, чтобы некоторый объект мог удалить другой объект, будучи уверенный, что на удаляемый объект более нет ссылок? Нужно ли это делать, ведь подобная синхронизация замедляет работу всей системы?

Я разберу этот вопрос, и в будущем постараюсь его осветить более подробно, но сейчас очевидно только одно – нельзя в многопоточной асинхронной программе оперировать непосредственными указателями на объект без специальных алгоритмов синхронизации. Ведь если удалить объект, используя один из указателей, то значения всех остальных указателей не изменятся, но они станут недействительными, и при любом из следующих обращений к ним произойдет крах программы.

Но допустим, что многопоточность в языке организована не на основе асинхронного обмена сообщениями, а на основе синхронного взаимодействия. Необходимо ли системе и тогда использовать алгоритмы отслеживания ссылок? По моему мнению – да. Ведь если требуется явно удалить объект в указанной точке, необходимо гарантировать, что удаляемый объект не будет в этот момент взаимодействовать с какими-либо другими. В многопоточной среде для этого необходимо использовать синхронизацию и удаляющий процесс должен ждать, когда удаляемый объект завершит все взаимодействия. К чему могут привести подобные ожидания? Допустим, что удаляемый объект a взаимодействует с некоторым объектом b, который взаимодействует с удаляющим объектом c. Так как приложение многопоточное, то существует вероятность того, что c начнет удалять a тогда, когда a уже соединился с b, но b еще не обратился к c. Объект b не может взаимодействовать с c, так как c ждет а и именно поэтому объект c не может перейти в необходимое состояние для взаимодействия с объектом b. Классический пример взаимной блокировки… Конечно удаление объектов – это не единственный случай, где может произойти взаимоблокировка при синхронизации, но если благодаря автоматическому слежению за ссылками на объекты можно избежать некоторого количества подобных ситуаций, то не лучше ли так и поступить?

Исторически так сложилось, что механизм отслеживания ссылок (автоматического управления памятью) называется «Garbage collection» (сборка мусора). Из-за этого даже разгораются споры относительно того, что если программа написана правильно, то ей нет необходимости использовать «сборку мусора» так как этого самого «мусора» не должно оставаться. Но сейчас другая ситуация – «сборка мусора» не просто удобный механизм, который облегчает создание программ, или позволяет избежать некоторых ошибок – этот механизм просто необходим для возможности написания многопоточных программ.

Остался только один вопрос. Почему я, столько говоря об автоматическом отслеживании ссылок на объект, в последней строке кода явно вызываю функцию удаления объекта, ведь в соответствии с правилами C++ для объекта wall будет вызван деструктор при выходе из текущего блока? С одной стороны это связано с тем, что сам C++ не предоставляет встроенных алгоритмов сборки мусора, а с другой – в текущей реализации библиотеки act-o используется алгоритм подсчета ссылок, который не может определять циклы в графе зависимости. В настоящее время я исследую вопрос о возможности улучшить этот алгоритм, либо заменить его каким-либо альтернативным алгоритмом управления памятью.

воскресенье, 12 августа 2007 г.

Что такое Inversion of Control?

Согласно этимологической справке, данной Мартином Фаулером, термин Inversion of Control (IoC) впервые был употреблен в статье Ральфа Джонсона и Брайана Фута «Designing reusable classes» в 1988 году. К сожалению, ни Фаулер, ни авторы упомянутой статьи не дают строгого определения этого термина, а предлагают его выводить из описания различия между такими понятиями как «библиотека» и «фреймворк». Вот что пишет Фаулер:

IoC – это то, чем определяется различие между фреймворком и библиотекой. По-существу, библиотека – это набор функций, которые можно вызывать из своей программы, сегодня обычно оформленный в виде классов. При каждом своем вызове функция проделывает некоторую работу и возвращает управление обратно клиенту.

Фреймворк, в свою очередь, определяет абстрактный прототип для некоторой задачи, с большим количеством встроенной логики. При использовании фреймворка программисту необходимо добавить логику своей частной задачи, как расширение, в различные области фреймворка с помощью наследования или подключения обработчиков. Код фреймворка самостоятельно вызывает пользовательский код в необходимые моменты.


Но, возможно, Фаулер дает самое точное из самого популярного определения. Если поискать в Google, то может сложиться впечатление, что IoC – это некоторый принцип, который характерен, во-первых, для объектно-ориентированного программирования (ООП), а, во-вторых, для больших программных компонентов (фреймворков). Более того, согласно статье в Википедии, термин IoC недостаточно четко определен, и многие попытки сделать это заканчиваются неудачей, так как нет четкого соглашения о сущности взаимодействия между объектом с одной стороны, и библиотекой, фреймворком или контейнером – с другой.

Я же считаю, что можно дать четкое определение того, что такое IoC. Более того, из определения будет видно, что IoC характерен не только для ООП и описания взаимодействия между классом и библиотекой или фреймворком, но и для любых стилей построения программ (процедурный, распределенный на основе сообщений), придерживающихся некоторого общего принципа.

Формальное определение IoC

Условия: Даны две сущности A и B, такие, что A зависит от B, но B не зависит от A. Такая зависимость означает, что сущность B может быть использована независимо от A, но A не может быть использована независимо от B. Рассматриваемыми сущностями могут быть классы, модули, библиотеки и любые другие программные сущности, которые экспортирую некоторое множество функций. Назовем экспортируемые множества функций Fa и Fb соответственно.

Определение IoC: Для данных в условии сущностей A и B, определяющих программу, существует обратный поток управления, если для некоторого набора входных параметров выполняются следующие условия.
  1. Выполнение программы начинается с некоторой функции x принадлежащей Fa .
  2. Существуют функции x и z принадл. Fa и некоторая функция y принадл. Fb, такие, что вызов функции x ведет к вызову функции y, которая, в свою очередь, вызывает функцию z. При этом функции x и z могут быть одной и той же функцией.
Пояснения и следствия

Необходимым условием наличия IoC является односторонняя зависимость сущности A от B, и соответственно множества функций Fa от Fb. Ведь только при наличии такой зависимости невозможен явный (прямой) вызов функции z принадл. Fa из функции y принадл. Fb. А так как прямой вызов невозможен, то такой вызов возможно осуществить только косвенно, каким-то образом передав адрес функции z в функцию y.

Однако не каждый косвенный вызов можно считать IoC. Так некоторая функция f может рекурсивно вызывать сама себя посредством косвенного вызова. Но это не будет считаться IoC, так как невозможно, чтобы одна функция входила в два непересекающихся множества.

Также не в каждом множестве функций может возникнуть ситуация обратной передачи управления. Рассмотрим полное множество Fb из определения, состоящее более чем из одной функции. И хотя возможен случай, когда данное множество возможно разделить на несколько непересекающихся подмножеств с односторонней зависимостью, я утверждаю, что ни одна – ни прямая, ни косвенная последовательность вызова этих функций не образует IoC. Так как множество функций заранее известно, то все косвенные вызовы всегда можно переписать в прямые. И только тогда, когда сущность B описывается так, что впоследствии ее может использовать некоторая сущность А, то невозможно переписать все косвенные вызовы в прямые, так как множество функций Fa в момент создания сущности B может быть неизвестно.

Невозможно также пройти стороной следующие два вопроса. А что, если множество Fb будет сконфигурировано так, что y будет вызывать не функцию z принадл. Fa, а некую функцию v принадл. Fb, либо w принадл. Fc? Можно ли такую ситуацию тоже назвать Inversion of Control? Я считаю, что если так поступить, то мы опять вернемся к тому, с чего начали – с вопроса о том, чем IoC отличается от косвенного вызова. Чтобы этого не произошло, будем считать, что IoC – это только такая ситуация, когда управление переходит в то множество, из которого был первоначальный вызов.

И так, можно говорить, что IoC образуется при связывании двух непересекающихся множеств функций Fa и Fb таким образом, что выполнение начинается с некоторой функции x принадл. Fa, передается в функцию y принадл. Fb из которой потом возвращается обратно в функцию z принадл. Fa. Реализуется посредством косвенного вызова или позднего связывания. При этом множество функций Fb специально реализовано так, чтобы быть впоследствии связанно с некоторым другим множеством функций.

В заключении хочу отметить, что согласно данному мной определению, такая стандартная и всем известная С-шная функция, как qsort реализована в полном соответствии с принципом Inversion of Control.

среда, 18 июля 2007 г.

О моделях актеров

Сегодня, как и обещал в своем первом сообщении, я расскажу о модели актеров. Однако, и это очень важно, я не ставил и не ставлю себе задачу просто взять ту модель актеров, которая была предложена изначально и сделать ее реализацию, так же как я не занимаюсь продвижением изначальной модели. Наоборот, я ставлю себе задачу переосмыслить ее под сегодняшние реалии, привнести в нее свой свежий взгляд и уже новую модель воплощать в коде и железе.


Модель актеров

Впервые модель актеров была предложена в работе «A Universal Modular ACTOR Formalism for Artificial Intelligence» тремя соавторами – Carl Hewitt, Peter Bishop, and Richard Steiger в 1973 году.

Модель актеров следует философии, что все есть актеры. Это очень близко к идее Smalltalk и других объектно-ориентированных (ОО) языков, что все есть объекты. Однако, в то время как, программы на большинстве ОО языках выполняются последовательно, в модели актеров все выполняется полностью параллельно.

Актер – это вычислительная единица, которая в ответ на принятое сообщение может сделать следующее:
  • послать ограниченное количество сообщений другим актерам;
  • создать ограниченное количество новых актеров;
  • изменить способ реакции на последующие обрабатываемые сообщения.
Актер не должен делать никаких предположений относительно того, в какой последовательности поступают сообщения от других актеров.

Взаимодействие между актерами осуществляется исключительно асинхронно. Это означает, что актер, пославший сообщение не ждет, когда получатель его обработает, а продолжает выполнять свои внутренние вычисления.

Получатель сообщения определяется только по адресу. Иногда такой адрес называется «почтовый адрес». Таким образом, актер может взаимодействовать только с теми актерами, адреса которых ему известны. Сам же актер может получить адреса других только из пришедших сообщений, также он по определению знает адреса тех актеров, которые были созданы им самим.

Как и идеи, заложенные в Smalltalk, идеи модели актеров просты и универсальны. К сожалению, их прямая реализация в виде какого-либо языка на подобии Smalltalk абсолютна неэффективна. И неэффективность следует из слишком большой общности и недетерминизма модели. В этом смысле можно отметить, что язык программирования Erlang реализует модель, близкую к модели актеров, но асинхронное взаимодействие осуществляется только между процессами (аналог актеров), в то время как взаимодействие внутри процесса осуществляется синхронно.


Распределенная модель актеров (модель активных устройств)

С физической точки зрения распределенную сеть образуют устройства – персональные компьютеры, КПК, смартфоны, датчики, сенсоры и т.д. и т.п. Однако чтобы эти устройства могли взаимодействовать, необходимо ввести некоторую логическую модель. Назовем каждое устройство – узлом сети. В свою очередь каждый узел представляется единичным актером. Если одно устройство выставляет в сеть два и более актера, то оно считается за два и более узла соответственно. Важное ограничение для этого – внутреннее состояние одного актера никак не должно сказываться на внутреннем состоянии другого. Иначе это нарушит модель и приведет к труднопрогнозируемым побочным эффектам, так как модель постулирует, что влияние одного актера на другой может осуществляться только с помощью посылки сообщений. Конечно, никакими средствами из внешней среды невозможно гарантировать, что устройство предоставит два абсолютно независимых актера, но оно должно придерживаться данного правила, если необходима корректная работа всей системы.

При организации больших распределенных систем, либо систем с большим количеством актеров, устройства могут объединяться в логическую единицу – группу или кластер. Данный кластер будет выступать как единичный узел по отношению к актерам, находящимся за его пределами. Физически же будет выделен один узел, который будет взаимодействовать с внешним окружением с одной стороны, и с внутренней структурой кластера – с другой.

Для того чтобы с удаленным актером можно было взаимодействовать, необходимо иметь представление о нем. Для этого необходимо описание. То есть представление текущего актера о тех с кем он взаимодействует – фантомные актеры. Данное описание будет представлять собой чистую концепцию объектно-ориентированного программирования (ООП). Объекты, интерфейсы, состояния и сообщения. Однако для надежного взаимодействия недостаточно описать только интерфейс удаленных актеров, необходимо еще описать и протокол этого взаимодействия, т.е. последовательность принимаемых и отправляемых сообщений, а также ожидаемую реакцию на них.

Распределенная модель имеет одно очень существенное отличие от обыкновенной программы – нет среды выполнения для объектов. Представьте музыкальный центр, который, в сети устройств, логически представлен одним актером. Невозможна функция, которая бы создала массив из десяти таких музыкальных центров. Также как невозможна функция сохранения этого актера в базу данных.

Классическое ООП моделировало объекты реального мира внутри программы, сейчас же стоит задача взаимодействие программы (актера) с объектами реального мира. Но не просто одиночного взаимодействия, а организации и управления множеством объектов реального мира – экосистемами актеров.

Распределенная модель актеров, в моем понимании – это не кластерная модель. Это не модель распределенных вычислений в смысле возможности вычислить какую-либо функцию либо выполнить какой-либо код на удаленном компьютере.

Задача распределенных вычислений стоит ортогонально к распределенной модели активных устройств. Это очень заманчиво, когда есть возможность использовать вычислительные мощности компьютера, соседнего по локальной сети. Но когда я беру в руки смартфон, то, прежде всего он интересует меня как устройство, с помощью которого я могу связаться с кем-то или чем-то, удаленным от меня на значительное расстояние. И только потом меня интересует тот факт, что в смартфоне есть процессор, который можно, во время бездействия самого устройства, использовать для разложения какого-либо числа на простые множители.


Локальная модель

В отличие от распределенной модели, которая используется для организации взаимодействия множества независимых устройств, локальная модель предназначена для описания параллельных алгоритмов внутри отдельно взятого устройства.

При этом между локальной и распределенной моделью проходит четкая граница – адресное пространство. Если актеры существуют в одном адресном пространстве и передача сообщений может осуществляться простой передачей указателей на область памяти, то это локальная модель, иначе – распределенная. Общей же между этими моделями является идея – организация взаимодействия между объектами (активностями, которые могут выполняться параллельно и независимо) с помощью асинхронной посылки сообщений.

Стоит также отметить, что логика устройств, участвующих в распределенном взаимодействии, совершенно не должна быть реализована при помощи локальной модели актеров. Может, но не должна. Дальнейшие исследования как раз и призваны показать – как, где и в каких случая необходимо или допустимо применять данную модель, а в каких – нет.

Хочу подчеркнуть, что библиотека act-o реализует именно локальную модель актеров.


Направления исследований

В заключение этой заметки намечу направления исследований, которые необходимо осуществить с целью более глубокого понимания модели актеров, ее применимости и способов, которыми она должна применяться.
  • Распределенная модель множества взаимодействующих актеров. Имеются в виду классы системы, где взаимодействующие элементы могут быть как независимыми, так и группироваться в кластеры, для осуществления общего и согласованного функционирования экосистемы этих элементов.
  • Распределенная модель взаимодействия на основе сообщений. Построение вычислительных алгоритмов на основе асинхронной недетерминированной модели обмена сообщениями. Также необходимо определить границы применимости этой модели и альтернативы для организации распределенных вычислений.
  • Языки описания взаимодействия актеров. Очень большая область, которая включает в себя языки описания локальных актеров, языки описания распределенных экосистем устройств, языки описания распределенных алгоритмов, а также языки описания взаимодействия всех этих актеров и экосистем как единого целого.
  • Самоорганизация и централизованное управление. Модели организации экосистемы устройств. Самоорганизующиеся модели актеров, модели с централизованным, управлением, и их смеси.
Оставайтесь на связи... :-)

среда, 11 июля 2007 г.

Планы на будущее

Что-ж, библиотека act-o развивается своим чередом, и настало время подумать о перспективах…

Это даже будет не столько перспектива, сколько видение. Потому что для любого человека очень важно определить, куда он идет. Тот самый пресловутый смысл жизни, хотя сейчас правильнее будет сказать – деятельности, но в данном случае не деятельности вообще, но своей профессиональной деятельности в частности.

Сегодня я задался простым вопросом, а что такое кризис программирования? И Google в очередной раз пришел мне на помощь. Очень скоро я набрел на перевод статьи Эдсгера Дейкстры «Смиренный программист» (1972). Две небольшие цитата из статьи.

Но я назвал это второстепенной причиной; первостепенная же кроется в том, что... машины стали на несколько порядков мощнее! Говоря прямолинейно: пока машин не было вовсе, не существовало проблемы программирования; когда появились немногочисленные слабые компьютеры, программирование стало малозаметной проблемой; а теперь, когда у нас есть гигантские компьютеры, программирование само превратилось в гигантскую проблему. В этом смысле электронная промышленность не решила ни единой проблемы, она только породила их, создав проблему использования своей продукции. Другими словами, по мере того как мощность доступных машин выросла более чем в тысячу раз, стремление общества найти им применение выросло пропорционально, и бедный программист вынужден метаться буквально по минному полю. Возросшая мощь оборудования совместно с, возможно, еще более возросшей надежностью, сделали возможными решения, о которых программист даже не отваживался мечтать несколько лет назад. А теперь, спустя несколько лет, он должен мечтать о них и, более того, он обязан воплощать эти мечты в реальность! Удивительно ли, что мы оказались в кризисе программирования?

Теперь переходим к пятому аргументу. Он относится к влиянию инструментов, которые мы пытаемся использовать, на наш образ мышления. Я наблюдаю культурную традицию, которая, по всей вероятности, уходит корнями в эпоху Возрождения: игнорировать это влияние и рассматривать человеческую мысль как первичную и главенствующую над тем, что ей создано. Но когда я начинаю анализировать свои собственные мыслительные привычки и привычки моих друзей, я прихожу, нравится мне это или нет, к совершенно иному заключению, а именно: инструменты, которые мы пытаемся использовать, и язык и обозначения, которые мы используем для выражения или записи наших мыслей, являются основным фактором, который определяет, о чем мы вообще можем мыслить и что можем выразить!


Обратите внимание на год публикации – 35 лет прошло. Думаете, что-то изменилось за это время?

Кризис как был, так и остался, и предлагается много способов создания больших программных систем, их разработки и описания. Но сколько я ни читал материалов, я ни разу не видел, чтобы автор задался вопросом: «А как нам вообще удается это делать?» Не помню, чтобы кто-то говорил, что необходимо обратиться к психологии, в частности к когнитивной психологии, чтобы исследовать способы, которыми человек вообще что-то описывает.

Я смотрю на это так. Программа – это описание системы. Разработка и проектирование – это в первую очередь мышление – это еще глубже в психологию и нейрофизиологию. А во вторую – это опять же описание проекта системы. И для процесса описания можно поставить три вопроса: Кто описывает? Что описывается? Как описывается? По-другому это можно перечислить, как – субъект, объект и способ. С объектом дела обстоят достаточно хорошо. Это как раз, классы алгоритмов, классы задача и классы систем. Подавляющее количество исследований проводится именно на эти темы. О субъекте – тишина. Остается способ. Это языки программирования и описания. Но способ – сущность зависимая. И зависит она от свойств субъекта и объекта. Но так как о субъекте никто не вспоминает, то можно констатировать только то, что способ – это здание с фундаментом наполовину.

В этом, как мне кажется, и заключается кризис программирования. Мало кто понимает, с чем мы на самом деле имеем дело. А дело мы имеем с человеческим мышлением, с его принципом и способом. И, мне думается, если копать в этом направлении, то можно найти решение не только для кризиса программирования, но для многих других вопросов… Необходимо начать рассматривать человека, как человека, а не как ресурс, или ещё одно исполняющее устройство…

Но это всё теория, а теперь немного о практике.

Теория об описании применима к любым программным системам и когда она появится, прорыв будет по многим фронтам, но меня больше интересуют мультиагентные системы, которые будут образовывать окружающие нас устройства, с целью сделать нашу жизнь приятной и комфортной. И как все знают, эти устройства будут самоадаптирующимися и интеллектуальными. Но они по определению не должны обладать настоящим интеллектом и тем более сознанием. Это очень просто – ваша микроволновка разогревает вам суп только потому, что у ней до сих пор нет интеллекта, а вот если бы он был, то она быстренько бы задалась вопросом: «А оно мне надо?» Скорее всего, это быстро превратится в апокалипсис на подобии того, о котором рассказывается в «Терминаторе»… Поэтому, экосистемы устройств всё равно придётся проектировать, конструировать и, в конечном счете, описывать.

В свою очередь эта часть обладает просто колоссальной практической значимостью. Когда мобильные устройства обмениваются только картинками и звуковыми файлами, то не страшно, если парочка из них сломается, либо начнет работать неправильно. Но когда от них начнет зависеть буквально всё – транспорт, системы охраны, жизнеобеспечения и даже кухня будет роботизированной, то любая ошибка потенциально может привести к человеческим жертвам. Это абсолютно недопустимо. И это будет кризис программирования даже не в квадрате, а в кубе…

Библиотека act-o тоже не будет забыта в ближайшее время. Как говорится: «Даже самый длинный путь начинается с одного маленького шага». Это хорошая база для того, чтобы получить начальное представление о мультиагентных системах.

Вы спросите: «А когда же начнется работа?». Так она уже идет, и будет продолжаться и впредь. И я буду рад сотрудничать с каждым, кто разделяет мое видение и готов работать над совместными проектами.
Оставайтесь на связи, и узнаете еще очень много интересного… :-)

понедельник, 9 июля 2007 г.

ООП в многопоточной среде: Введение

Как вы знаете, одним из принципов современного ООП является «инкапсуляция». Он означает, что только методы данного класса могут обратиться к закрытым полям данных этого класса. Конечно, есть еще и закрытые методы, но сейчас нас интересуют только данные. Основной задачей инкапсуляции является локализация мест, которые влияют на состояние объекта. С точки зрения компилятора такая локализация достигается с помощью ограничения области видимости закрытых полей данных, т.е. поля доступны только методам этого же класса.

Методы, связанные с классом хорошо справляются со своей задачей, но когда мы используем ООП в многопоточной среде, то помимо классов и объектов появляются еще и потоки. В языке С++ нет такого понятия, как поток, а следовательно компилятор не делает каких-либо проверок относительно них. И ситуация начинает повторяться. Хоть изменение состояния и локализовано в методах, но сказать когда и в каком потоке будет выполняться этот метод в общем случае невозможно. Потоки как бы проникают сквозь барьер инкапсуляции, и это может разрушить состояние объекта. В теории компьютерных наук такая ситуация называется гонками – ситуация, в которой два (и более) процесса считывают или записывают данные одновременно, а конечный результат зависит от того, какой из них был первым.

Чтобы избежать такой ситуации в Java, например, есть такой атрибут для методов как synchronized, а в языках С++ и Delphi таких атрибутов нет, и поэтому, необходимо код каждого публичного метода защищать критической секцией.
Мой опыт создания ядра библиотеки act-o говорит о том, что это очень плохая идея – писать программы в стиле ООП доступном на С++, защищая публичные методы критическими секциями. Потому, что почти сразу я столкнулся с явлением взаимоблокировки и разобраться, что и почему блокируется, было просто невозможно. Я отказался от ООП в пользу процедурного стиля.

Один из возможных способов избежать вышеописанных проблем – это вернуться к первоначальной концепции ООП, предложенной Аланом Кеем.
Алан Кей – это человек, который создал язык программирования Smalltalk, и ввел термин «объектно-ориентированный». Тогда он вкладывал в этот термин три основных идее:
  • Объект – базовая единица объектно-ориентированной системы.
  • Объекты могут обладать состоянием.
  • Посылка сообщений – единственный способ обмена информацией между объектами.
Для нас сейчас важен последний пункт – «посылка сообщений». Если организовать взаимодействие между объектами с помощью сообщений, а не через вызовы методов (как в C++), то удастся решить проблемы с инкапсуляцией и гонками. Только необходим настоящий обмен сообщениями – асинхронный, посредством организации очереди.

Благодаря очереди сообщений становится не важно, какой поток послал сообщение, важно только в каком потоке оно будет обработано. В результате инкапсуляция не нарушается. А, вводя в систему правило, что объект может обрабатывать только одно сообщение в одном потоке одновременно, отпадает необходимость в наличии критических секций в методах обработки сообщений. В результате не возникает ситуация гонок.

Все это краткое описание тех идей, на основе которых построена библиотека act-o, и именно такой механизм организации взаимодействия между объектами она предоставляет.

В недалеком будущем я опубликую небольшое исследование об истории ООП и о том, как оно от определения, данного Аланом Кеем, превратилось в то, чем оно является сейчас. Похоже, что модель актеров – это начальная идея ООП, но в новой обертке.
Также расскажу о таком понятии как Inversion of Control (IoC) и почему оно тоже может представлять проблему не только в многопоточном ООП, но и в классическом.
Оставайтесь на связи… :-)

четверг, 5 июля 2007 г.

«The act-o Library»

Это мой первый пост, и я рад, что это связанно с очень хорошей новостью. Мой проект «The act-o Library» наконец-то увидел свет. Сегодня я загрузил ее по адресу официального размещения http://sourceforge.net/projects/act-o Там же будут появляться и все последующие релизы пакетов.

«The act-o Library» или просто act-o – это библиотека классов на языке C++ и для программистов на языке C++, которая призвана помочь им в нелегком труде создания многопоточных приложений, с целью максимально задействовать потенциал современных многоядерных процессоров. В основу библиотеки положена модель актеров. Так как в ядро не закладывался функционал взаимодействия актеров на разных компьютерах или в разных процессах, то нет ни работы с сокетами, ни сериализации ни других подобных вещей. Поэтому, все получилось очезнь просто, компактно и быстро. А главное надежно, гарантированно и типобезопасно…

Конечно, это еще beta-версия, и в первый пакет включена не вся функциональность, которую я подготовил, но библиотека уже находится в том состоянии, когда ее можно пощупать, понять, чем она является, и на что по завершению она будет похожа.

В ближайшее время я более подробно опишу, что вообще представляет собой библиотека act-o и ту модель, которая положена в основу библиотеки. И по ходу буду информировать вас обо всех существенных изменениях и событиях, связанных с библиотекой. Надеюсь, что она будет вам полезна.
Оставайтесь на связи… :-)