Skip navigation.

проФорт

Форт и всё такое

Простой сканер портов - 3

,

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

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

Посмотрим как это работает на практике. Реализация самой модели в ~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'а, точнее из прочитанных описаний :smile:

Ссылки по теме :

Конечные автоматы и таблицы решений на SPFSummary 2007

Comments

ac 15. January 2008, 15:36

Вместо ~ygrek/lib/multi/msg.f можно использовать ~pinka/lib/multi/ack_messages.f , тогда действительно "становятся невозможными дедлоки, т.к. нет самого понятия локов" (в игрековой либе локи есть). Или пул рабочих потоков, принимающих работу "сообщениями" - ~ac/lib/win/thread/pool.f

exs 16. January 2008, 20:16

Я эту либу Рувима обнаружил уже после того как. Но там ограничение на размер передаваемых данных - только поля структуры MSG. Можно использовать как транспортную среду для msg.f, очевидно побыстрее будет.
Насчёт "невоможности дедлоков" - тут дело не в том есть ли в явном виде мутексы в коде или нет, а в том что они скрыты за таким интерфейсом который не позволит задедлочиться ни в каком случае. Наверняка ведь и в реализации KERNEL32::GetMessage есть внутри мутексы.

How to use Quote function:

  1. Select some text
  2. Click on the Quote link

Write a comment

Comment
(BBcode and HTML is turned off for anonymous user comments.)

If you can't read the words, press the small reload icon.


Smilies