Простой сканер портов - 3
By exs. Sunday, 16. September 2007, 14:46:08
В прошлый раз мы запустили сканирование портов из нескольких потоков, при этом нам пришлось решать основную проблему многопоточных приложений - организацию эксклюзивного доступа к ресурсам. В этот раз я покажу другой способ решения этой проблемы, без критических секций и явного "захвата" ресурса рабочими потоками. А именно с помощью потоков-владельцев и обмена асинхронными сообщениями.
Суть идеи заключается в том что для того, чтобы исключить ситуацию когда несколько потоков запрашивают один ресурс, у каждого ресурса изначально есть поток-владелец который единолично имеет к нему прямой доступ. "Рабочие" потоки - те которые выполняют полезную работу - не могут получить прямой доступ к ресурсам, а лишь просят нужных владельцев выполнить какую-то работу с данным ресурсом. Единственная возможность оформить эту просьбу - послать сообщение, единственная возможность получить какие-то данные от владельца ресурса - принять сообщение. При этом все сообщения асинхронные и передаваемые данные полностью содержатся в сообщении - никаких указателей, только сериализация - для того чтобы опять не наступить на те же грабли с синхронизацией доступа.
Посмотрим как это работает на практике. Реализация самой модели в ~ygrek/lib/multi/msg.f (либа сырая, но для показательного примера вполне сгодится). Слова интерфейса :
-
ltreceive ( -- msg ) вызывается внутри потока для получения очередного сообщения из очереди (блокирует если сообщений нет).
-
msg.data ( msg -- a u ) извлекает текст сообщения (все данные передаваемые между потоками должны сериализоваться в строку, конечно я не могу запретить передавать указатели, но это сводит на нет все преимущества этого подхода - независимость потоков и унификацию средств общения).
-
msg.type ( msg -- type ) возвращает тип сообщения (на разные собщения можно реагировать по разному).
-
msg.sender ( msg -- lt ) возвращает идентификатор потока который послал это сообщение (для обратной связи).
- ltsend ( a u type lt -- ) посылает сообщение указанному потоку и возвращает управление немедленно, не ожидая обработки получателем (сообщение ставится в очередь получателя).
В нашем сканере работают потоки трёх типов - port-supplier, scanner и registrator.

port-supplier заведует ресурсом непросканированных портов и выдаёт их по запросу тому кто присылает любое сообщение, вот его цикл работы :
:NONAME { | m range_low range_hi }
\ some initialization here
BEGIN
ltreceive -> m
range_low range_hi = IF ELSE range_low DUP 1+ -> range_low THEN
" {n}" m msg.sender STRltsend
m FREE-MSG
AGAIN ; VALUE <port-supplier>
Слово STRltsend ( s n lt -- ) это очевидная обёртка над ltsend.
Потребители этого ресурса - потоки-сканеры, в цикле запрашивают порт и сканируют его, докладывая о результате в поток-регистратор сообщением MSG_REGISTER. При завершении работы (когда supplier не даёт больше новых портов) регистратору посылается сообщение MSG_FINISHED чтобы он мог отследить момент когда все сканеры завершатся :
:NONAME
\ some initialization here
BEGIN
S" " port-supplier ltsend
ltreceive -> m
m msg.data NUMBER 0= ABORT" Supplier is mad!" -> port
port \ пока есть порты для проверки
WHILE
hostname STR@ port tryConnect IF port " {n}" MSG_REGISTER registrator STRltsend THEN
REPEAT
S" " MSG_FINISHED registrator ltsend
; VALUE <scanner>
Как только регистратор получает MSG_FINISHED от всех запущенных сканеров, он отчитывается о зарегистрированных портах и завершает работу программы.
Полный текст программы
Посмотрим что изменилось по сравнению с предыдущей версией. Во-первых пропали все манипуляции с критическими секциями и памятью потоков. Во-вторых в глобальных переменных остались только идентификаторы задач, сами ресурсы спрятаны внутрь соответствующих потоков - к ним нет другого доступа кроме как послать сообщение. Это очень хорошо - меньше связей, меньше сложности. Кстати, при такой организации взаимодействия потоков становятся невозможными дедлоки, т.к. нет самого понятия локов..
Cама идея была скопирована из erlang'а, точнее из прочитанных описаний
Ссылки по теме :







ac # 15. January 2008, 15:36
exs # 16. January 2008, 20:16
Насчёт "невоможности дедлоков" - тут дело не в том есть ли в явном виде мутексы в коде или нет, а в том что они скрыты за таким интерфейсом который не позволит задедлочиться ни в каком случае. Наверняка ведь и в реализации KERNEL32::GetMessage есть внутри мутексы.