А. Скробов
Использование волокон ("фиберов") в VB для одновременного выполнения нескольких задач
Примеры к статье
Обсудить в форуме
В Win98+/WinNT35+ доступны фиберы (волокна, fibers - ред.), т.е. выполняемые задачи, управление которыми не осуществляется системой, а возложено на программиста. Говорят, они были введены в WinNT для облегчения переноса UNIX-приложений, где существовало несколько видов нитей, как управляемые системой, так и управляемые программистом.
В соответствии с "текстильной" аналогией, процесс состоит из нитей, а нити - из фиберов. Аналогия, впрочем, неполная: у каждого процесса есть по крайней мере одна нить, тогда как нить может не иметь фиберов вовсе. Первый создаваемый в контексте нити фибер - это, по сути, сама эта нить; к её уже существующему контексту нити добавляется контекст фибера.
У каждого фибера, как и у нити, сохраняется контекст, т.е. внутреннее состояние - значения регистров и т.п. Однако фиберы выполняются строго попеременно, из-за чего отпадает необходимость в запутанной синхронизации задач, которая так усложняет разработку многонитёвых приложений. Переключения между фиберами осуществляются не по инициативе системы, а по запросу программиста. Фиберы сродни "сопрограммам" в книге Кнута "Искусство программирования", т.е. процедурам с несколькими точками входа и несколькими точками выхода.
Фиберы существенно "дешевле" в использовании, чем нити; контекст фибера не содержит таких данных, как, например, приоритет или права доступа, и поэтому занимает меньше места в памяти. Кроме того, с их использованием сопряжены меньшие затраты времени на переключение контекстов, т.к. эти переключения происходят только тогда, когда они необходимы; например, раз в несколько секунд, а не тысячи раз в секунду, как в случае с переключением нитей.
Последнее их свойство может оказаться и недостатком: например, во время длительного вызова функции API фибер "заблокирован" и не может передать управление другому фиберу, пока не произойдёт возврат в основную программу. В случае же с нитями, напротив, переключение контекста может произойти посередине вызова функции API.
Ещё одно существенное различие фиберов и нитей - в том, что на многопроцессорной системе несколько нитей одного процесса могут одновременно выполняться на разных процессорах. Фиберы, в свою очередь, выполняются строго попеременно; даже если есть несколько свободных процессоров, многофиберная нить будет выполняться только на одном из них. На остальных процессорах в это время могут выполняться, например, фиберы других нитей того же самого процесса; фиберы разных нитей одного процесса никак друг с другом не связаны.
Поскольку многофиберные приложения могут быть и однонитёвые, они вполне реализуемы в VB. Однако, к моему удивлению ни одного примера такого приложения я в Интернете не нашёл. Придётся восполнить этот пробел.
Для примера рассмотрим одновременный поиск простых чисел и решений задачи Диофанта от Daz. Не самый жизненный пример, но уж какой есть :-)
Самое главное в этом примере - его, в принципе, можно было написать и на таймерах, - это удобство программирования. Вместо хранения счётчиков каждого цикла в статических переменных, мы пишем цикл именно так, как писали бы его без одновременно выполняемого другого.
Сложности с отладкой определённые есть, но я считаю их несущественными - например, нельзя делать Step Into (F8) на вызове SwitchToFiber; нельзя нажимать End, когда выполняется не главный фибер; и нельзя вызывать DeleteFiber для "недавно" выполнявшегося фибера. Причина последнего ограничения мне не вполне ясна, но оно имеет место быть. В общем же, многофиберный код работает прекрасно и под отладчиком, и в скомпилированном виде.
В примере для пущей эффектности выводится только каждое сотое простое число - иначе их список быстро переполняется.
Ещё несколько примечаний касательно применения фиберов стоят прямо в коде примера.
ПРИЛОЖЕНИЕ (краткая историческая справка)
Идея использовать фиберы в VB для имитации многонитёвости пришла мне в январе с.г. (http://groups.google.com/groups?selm=4264525367%40p2.f175.n5020.z2.ftn&oe=KOI8-R&output=gplain) Довольно быстро я получил работающее многофиберное приложение (из десятка строк), но прогресс застопорился из-за неожиданной проблемы: я так и не смог придумать, где многофиберность может понадобиться (http://groups.google.com/groups?selm=4290335804%40p2.f175.n5020.z2.ftn&oe=KOI8-R&output=gplain) Однако идея о том, что фиберы всё-таки могут быть полезны, дремала во мне до тех пор, пока Daz не предложил задачу Диофанта. Вот тогда-то мне и пришла в голову идея этого примера. Daz-у огромная благодарность :-)
Других примеров использования многофиберности в VB, кроме моего, гуглу неизвестно. Так что считаю себя первопроходцем :-)
Скачать архив с примерами
Файлы в архиве:
папка "Fibers1" - простейший пример использования фиберов в VB
папка "Fibers2" - пример объектно-ориентированного использования фиберов в VB
Две демонстрационные программы идентичны по выполняемым действиям, но в первой весь код содержится в одном модуле, а во второй - разнесён по классам.
папка "Fibers3" содержит готовый объектно-ориентированный менеджер фиберов, который поможет вам легко добавлять поддержку многофиберности в ваши программы.
вкратце о его использовании: объект "FiberManager" представляет собой циклическую очередь фиберов, которым по очереди передаётся управление.
Чтобы добавить новый фибер в очередь, нужно экземпляр объекта, реализующего интерфейс IFiber, передать в функцию ScheduleFiber.
Функция ScheduleFiber возвращает идентификатор фибера, который затем можно использовать при вызове метода UnscheduleFiber, удаляющего фиберы из очереди.
Третий метод объекта FiberManager, используемый главной программой - это FiberLoop, запускающий цикл работы фиберов.
В цикле работы фиберов управление передаётся фиберам в том порядке, в котором они были добавлены в очередь; после последнего фибера в очереди вновь запускается первый (рис. 1 - цветными квадратиками обозначены "вторичные" фиберы, кружком - главный фибер, в котором выполняется менеджер).
Рис. 1
Если во время работы цикла в очередь добавляются новые фиберы, то управление им будет передаваться после отработки всех старых фиберов.
Выход из цикла происходит тогда, когда все фиберы удалены из очереди - например, завершили свою работу, или когда свойство ExitLoop объекта FiberManager будет установлено в True. Это свойство используется в примере для остановки цикла при закрытии формы.
Два других метода объекта FiberManager - Yield и ExitFiber - могут вызываться только из "вторичных" фиберов. Метод Yield разрешает менеджеру передать управление следующему фиберу в очереди, а метод ExitFiber удаляет из очереди текущий фибер. Возврат из вызова метода ExitFiber не происходит.
Поскольку при запуске из-под IDE вызов DeleteFiber иногда приводит к падению программы вместе со средой, то при инициализации менеджера фиберов производится проверка на запуск из-под IDE, и в этом случае удалённые из очереди фиберы не удаляются из памяти. В скомпилированном коде вызов DeleteFiber не сопровождается подобными проблемами, и там "отработанные" фиберы корректно уничтожаются. Поскольку контекст фибера довольно маленький - порядка сотни байт - мне не кажется, что при отладке сохранение "мёртвых" фиберов в памяти будет иметь какой-либо эффект.
Дополнительные детали касательно использования этого менеджера фиберов смотрите в комментариях в примере. Этот пример, как и все предыдущие, одновременно ищет простые числа и решения задачи Диофанта.
Ещё одна возможность, не реализованная в примере, связана с тем, что менеджер фиберов оформлен в виде класса, а не модуля. Соответственно, один из "вторичных" фиберов может создать собственный менеджер и запустить в нём ещё один цикл работы фиберов, получая многоуровневую структуру (рис. 2).
Рис. 2
Используя "иерархию" менеджеров фиберов, можно контролировать "приоритет" фиберов - например сделать, чтобы один из фиберов вызывался вдвое чаще, чем другой.
Вместе с этим менеджером управление фиберами становится простым и понятным.
Автор: А. Скробов
WWW: http://cs.usu.edu.ru/home/skrobov/