Вверх ↑
Этот топик читают: Гость
Ответов: 4612
Рейтинг: 746
#31: 2017-05-15 14:31:34 ЛС | профиль | цитата
Galkov писал(а):
Вы так и не сказали, чем Вас не устраивает предложенный мной вариант
Звыняюсь, пытался впихнуть в текст мысли, которые возникли за выходные.
Galkov писал(а):
сколько стоять то будет: секунду, минуту, час, два дня ?
Решение должно не зависеть от времени выполнения кода, просто потому, что мы это время не можем контролировать.
Galkov писал(а):
Этого вполне достаточно, чтобы похоронить вариант "с флагом"
Нет, недостаточно. Достаточно проверять флаг по выходу из таймаута.
Более того, представим, что в onRead порт был закрыт и Stopped = True. Но в текущей реализации после строки _hi_onEvent(_event_onRead, ReadStr) идёт безусловный вызов событиея _event_onSyncRead. То-есть, нарушение логики - пользователь отключился от порта, а событие после этого произошло. Вывод - флаг остановки потока нужно проверять после каждого действия, которое может потребовать остановки, в данном случае - после каждого вызова внешнего события + после таймаута операции (так как есть большая вероятность, что пока поток стоял на таймауте, пользователь сделал doClose). Чем здесь помогут Event - не представляю.

Вопрос остаётся: как будем освобождать объект thrd в методе CloseCom?
Чисто теоретически, исходя из того, что у TThread есть счетчик ссылок, можно оставить как есть. Поток завершит свое исполнение, уменьшит счетчик, увидит, что он равен 0 и освободит объект.

Есть другой вариант - установить свойство TThread.AutoFree := True при создании потока.
Тогда освобождение делается так:
  if Assigned(thrd) then
  begin
thrd.Stop;
thrd := nil;
end;
И объект будет завершен, когда произойдёт выход из метода THICOMEX.ExecuteRd.
Я предпочитаю использовать этот вариант.

Да, я ошибся, в самом TThread увеличение счетчика нигде не происходит. Как можно использовать счетчик сейчас.
Сразу после создания thrd fRefCount=0. В THICOMEX.ExecuteRd мы должны увеличить счетчик (thrd.RefInc, на 2), тогда счетчик объекта равен 2-м. Когда мы вызовем free_and_nil(thrd) объект не будет уничтожен, только уменьшится счетчик. Затем если THICOMEX.ExecuteRd уменьшит счетчик (thrd.RefDec, на 2) - объект будет уничтожен. Или наоборот - сначала свою роботу завершит THICOMEX.ExecuteRd и уменьшит счетчик первый раз, затем free_and_nil(thrd) уменьшит счетчик второй раз и уничтожит объект. Вот только первый вариант, когда THICOMEX.ExecuteRd уменьшает счетчик последним и уничтожает объект, недопустим: там продолжает свое исполнение метод TThread.Execute, и он упадёт на доступе к полям уничтоженного себя.


Почему я написал, что реализация счетчика ссылок в KOL.TObj глючная.
Вот код метода, уменьшающего счетчик и и уничтожающего объект.

procedure TObj.RefDec;
begin
Dec( fRefCount, 2 );
if (fRefCount < 0) and LongBool(fRefCount and 1) then
Destroy;
end;

Существует вероятность коллизии (вроде это называется race condition - состояние гонки), когда этот метод будет вызван из двух разных потоков
Система по своему усмотрению переключает потоки и последовательность исполнения может быть такая:
procedure TObj.RefDec;
begin
Dec( fRefCount, 2 ); // 1-ый поток, после этого счетчик равен 0.
// Переключение потоков
Dec( fRefCount, 2 ); // 2-й поток, после этого счетчик равен -2.
if (fRefCount < 0) and LongBool(fRefCount and 1) then // 2-й поток, условие выполняется, объект уничтожается
Destroy;
// Переключение потоков
if (fRefCount < 0) and LongBool(fRefCount and 1) then // 1-й поток падает на доступе к fRefCount либо на Destroy, потому что объект уже был уничтожен вторым потоком
Destroy;
end;

И если в среднестатистических условиях вероятность, что два потока вызовут RefDec одновременно, не очень высокая, то она возрастает по мере роста активности потоков, например, в моём TCP-сервере, где есть много соединений, которые создаются и уничтожаются.
Кроме того, в нашем случае есть очень специфическая ситуация: когда мы делаем
thrd.Stop;
free_and_nil(thrd);

В этот момент метод THICOMEX.ExecuteRd может прочитать флаг Stopped=True и тоже устремится завершить свое исполнение и уменьшит счетчик.
И вот тут эти два потока оказываются очень близкими к конфликту за счетчик. Решением этой проблемы является использование атомарных операций со счетчиком InterlockedIncrement/InterlockedDecrement (а также, возможно, InterlockedCompareExchange, хотя я пока обходился).

Редактировалось 10 раз(а), последний 2017-05-15 15:29:15
карма: 26

0
Ответов: 9906
Рейтинг: 351
#32: 2017-05-15 15:26:20 ЛС | профиль | цитата
Netspirit писал(а):
после строки _hi_onEvent(_event_onRead, ReadStr) идёт безусловный вызов событиея _event_onSyncRead. То-есть, нарушение логики - пользователь отключился от порта, а событие после этого произошло

Не вижу нарушения логики, ибо никто не обещал, что onSyncRead можно предотвратить какими-то действиями по onRead.
Это, вообще-то - одно и тоже по логическому смыслу. Мы так раньше думали.
Скажу больше, мы обещали, что эти два события произойдут после приема. Прием произошел УЖЕ. После первого события, предположим, Вам расхотелось принимать далее.
Вы и не будете. Дальше. Но то, что УЖЕ пришло - соблаговолите забрать.
Вам никто руки не выламывал, сами подключились к обоим событиям.
И где здесь, спрашивается, нарушение логики...

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

Netspirit писал(а):
(так как есть большая вероятность, что пока поток стоял на таймауте, пользователь сделал doClose). Чем здесь помогут Event - не представляю.
Ну вы, блин, даете...

WaitForMultipleObjects - срывается с ожидания ЛИБО по OvrRd.hEvent, ЛИБО по EvRdStop (при bWaitAll=false, третий параметр).
Сразу же, безо всяких таймаутов.
Он так устроен, этот WaitForMultipleObjects.
Грубо говоря, он для этого и сделан был.

Редактировалось 2 раз(а), последний 2017-05-15 18:18:52
карма: 9

0
Ответов: 4612
Рейтинг: 746
#33: 2017-05-15 15:36:35 ЛС | профиль | цитата
Galkov писал(а):
А вот усмотреть логику закрывания порта по приему чего-то там по onRead - не получается у меня
В случае COM-порта - может и нет. Но мы обсуждаем глобальный паттерн использования параллельных потоков (я надеюсь, что кто-то вынесет для себя полезные моменты и применит их не только в HiAsm, а и в других местах). Например, такой глобальный смысл освобождения ресурсов по команде из параллельного потока есть в TCP компонентах, или, например, при работе с Pipe по отключению удаленной стороны.

Кроме того - а ты увидел в методе THICOMEX.ExecuteRd хоть какую-нибудь обработку ошибок? А прикинь, если кто-то выдернет твое устройство из порта, что будет делать параллельный поток? Он должен отловить ошибку, освободить все ресурсы и завершиться. Хотя я предполагаю, у COM-порта нет сигнала отключения, система просто будет и дальше ожидать от него прихода данных. Но я могу и ошибаться. И опять же, это применимо к COM-порту. А все остальные места использования потоков?

Редактировалось 2 раз(а), последний 2017-05-15 15:37:27
карма: 26

0
Ответов: 9906
Рейтинг: 351
#34: 2017-05-15 15:55:24 ЛС | профиль | цитата
Netspirit писал(а):
Почему я написал, что реализация счетчика ссылок в KOL.TObj глючная.

Вы написали совершенно неправильно.
Глюки есть, но они в мозгах того, кто сначала начинает вызывать методы объекта из разных потоков, а потом смотрит, что получится.
В такой логике практически все элементы в HiAsm - глючные.
А в правильной логике - данные ресурсы охраняются критическими секциями, или мьютексами.
И это есть проблема того, кто использует элементы, а не элементов.
карма: 9

0
Разработчик
Ответов: 26061
Рейтинг: 2120
#35: 2017-05-15 15:56:42 ЛС | профиль | цитата
Netspirit писал(а):
А прикинь, если кто-то выдернет твое устройство из порта

Если устройство воткнуто в USART, то по деревне. А вот что будет, если это будет USB-эмулятор, и выдернут будет на стороне USB, то ХЗ? Я лично не проверял.

--- Добавлено в 2017-05-15 16:03:19

Так на чем остановились, многоуважаемые кроты Че дальше делать будем, а то у меня от ваших мыслей уже у самого deadlock начнется?

Редактировалось 2 раз(а), последний 2017-05-15 16:03:19
карма: 22

0
Ответов: 4612
Рейтинг: 746
#36: 2017-05-15 16:06:39 ЛС | профиль | цитата
В общем, я так понимаю, что вызывать doClose из событий компонента запрещаем, потому что deadlock на thrd.WaitFor? А без WaitFor уничтожение объекта приведёт к падению потока.

Редактировалось 2 раз(а), последний 2017-05-15 16:08:16
карма: 26

0
Ответов: 9906
Рейтинг: 351
#37: 2017-05-15 16:16:37 ЛС | профиль | цитата
Netspirit писал(а):
Но мы обсуждаем глобальный паттерн использования параллельных потоков

Вообще-то, Вы подняли вопрос о некорректности работы CloseCom
Это совершенно справедливо, и это надо исправлять.

Далее, начался разговор из серии "хочу убить самого себя" (которому в обед сто лет будет) с переходом на "глобальные паттерны".
Так вот, если они действительно глобальные (т.е., не накладывающие ограничений на окружение), то другого выхода, как "таймерная развязка", или DeferredEvent -- пока не придумано.
Мне даже думается, что попытки изобретательства на эту тему - мало перспективны

--- Добавлено в 2017-05-15 16:30:14

Netspirit писал(а):
А без WaitFor уничтожение объекта приведёт к падению потока

Я бы сказал по другому...
С уничтожением объекта можно справиться, если сделать для потоков AutoFree=True.
Но у нас еще есть и CloseHandle(hFile) - который тоже не хотелось бы исполнять ДО завершения их обоих

--- Добавлено в 2017-05-15 16:34:24

Хотя с другой стороны - это фактически гарантированно, если приоритет потока выше основного...
Сразу же после SetEvent(EvRdStop) поток thrd должен быть убит нафиг и без остатка.
Вроде как.

Редактировалось 3 раз(а), последний 2017-05-15 18:22:23
карма: 9

0
Ответов: 4612
Рейтинг: 746
#38: 2017-05-15 16:41:30 ЛС | профиль | цитата
Galkov писал(а):
Мне даже думается, что попытки изобретательства на эту тему - мало перспективны
Но я то затеял этот разговор, потому что уверен, что решил эту задачу в своих TCP-компонентах и Pipe-компонентах. Надеялся, что выяснятся более подходящие варианты или обнаружатся недостатки.

Видео тестов стабильности Pipe-клиента и сервера: http://rgho.st/private/8SYT74jfT/97414d2c8f51787fc55d1fbf8fdaa1f7
Показывает, как на протяжении 3-х часов к серверу подключаются и отключаются клиенты, отправляют и принимают данные, причём нет никакого аномального потребления памяти и потоков, всё корректно удаляется и отрабатывает.
Сами компоненты с откомпилированными примерами: /topic/66399

Galkov писал(а):
который тоже не хотелось бы исполнять ДО завершения их обоих
Нет ничего сложного - правильно отрабатывать ошибки в THICOMEX.ExecuteRd. Например, мой Pipe-клиент можно элементарно подправить для работы с COM-портом: подправляется метод открытия, метод чтения и метод отправки данных, свойства и события класса. И да, если перед CloseHandle(hFile) выполняется thrd.Stop, то при получении ошибки в THICOMEX.ExecuteRd нужно проверить флаг Stopped - если True, значит нас хотят уничтожить, прекратить работу, не выдавать никаких событий. И да, в моих компонентах как раз используется AutoFree. Но, поскольку у меня не используется WaitFor, чтобы не было дедлоков, то максимально используется механизм подсчета ссылок для класса, который владеет объектом потока TThread - чтобы класс не был уничтожен, пока исполняется его метод в параллельном потоке.

--- Добавлено в 2017-05-15 16:50:36

Galkov писал(а):
поток thrd должен быть убит нафиг и без остатка
Тут не совсем понял - если имеется в виду "убить исполнение метода THICOMEX.ExecuteRd", то выше мы выяснили, что это недопустимо.

--- Добавлено в 2017-05-15 16:58:20

Кстати, в поправке Galkov-a по-моему, неправильно применяется функция WaitForMultipleObjects - нужно делать record с 2-мя полями, в которых будут лежать два Event, и указатель на record передавать в WaitForMultipleObjects.

Редактировалось 6 раз(а), последний 2017-05-18 14:22:48
карма: 26

0
Ответов: 9906
Рейтинг: 351
#39: 2017-05-15 18:07:45 ЛС | профиль | цитата
Netspirit писал(а):
Тут не совсем понял

Имелось в виду, что SetEvent снимает более высокоприоритетный поток с ожидания, а scheduler винды моментально передает ему управление.
И не отдаст обратно, пока поток не завершится полностью.
Но все это не срабатывает, если doClose запускается от onRead - приоритеты тогда одинаковые.

Netspirit писал(а):
потому что уверен, что решил эту задачу

Решить задачи в некотором частном случае можно, конечно же. Любой деструктор решает ее - метод объекта, который убивает сам себя.
И в COMex можно, пожалуй. Например, делать CloseHandle(hFile) прямо по окончании thrd (предварительно дождавшись окончания thwr).

Но это вовсе не разговор о глобальных паттернах.

Netspirit писал(а):
нужно делать record с 2-мя полями

Если заниматься буквоедством, то тип второго параметра - указатель на статический массив хэндлов. А не на record.
У меня там жульство применено, посмотри внимательней. OvrRd.hEvent и EvRdStop расположены в hiCOMex последовательно. Т.е., рядом: offset первого хэндла - 38h, второго - 3Ch

Редактировалось 4 раз(а), последний 2017-08-01 23:37:58
карма: 9

0
Ответов: 4612
Рейтинг: 746
#40: 2017-05-16 11:18:24 ЛС | профиль | цитата
Galkov писал(а):
OvrRd.hEvent и EvRdStop расположены в hiCOMex последовательно
Так не честно Я что-то подобное предполагал, но подумал, что OvrRd.hEvent не самое последнее поле OvrRd, но проверять было лень. Обычно в таких случаях комментарии надо писать.

Редактировалось 1 раз(а), последний 2017-05-16 11:18:33
карма: 26

0
Ответов: 9906
Рейтинг: 351
#41: 2017-05-16 17:22:38 ЛС | профиль | цитата
Дык, пытался:

...
OvrWr: TOverlapped;
EvWrStop: THandle; // ВАЖНО: сразу после OvrWr.hEvent
OvrRd: TOverlapped;
EvRdStop: THandle; // ВАЖНО: сразу после OvrRd.hEvent
...

карма: 9

0
Ответов: 4612
Рейтинг: 746
#42: 2017-05-17 12:16:31 ЛС | профиль | цитата
А-а-а, вот что оно значило.

Пока что на словах, такая мысля по поводу решения Galkov-а. Функция WaitForMultipleObjects возвращает индекс сработавшего Event. Если их несколько, то наименьший индекс. Это значит, что при активном приёме данных будет возвращаться индекс OvrRd.hEvent (=0), а не EvRdStop. Следовательно по выполнению THICOMEX.CloseCom функция WaitForMultipleObjects с высокой вероятностью завершится сигналом OvrRd.hEvent и пойдёт дальше на вызов onSyncRead (если подключен).
Так вот, вызов COMEX.doStop из главного потока пользователем (не из событий COMEX) приведёт к тому, что главный поток стаёт на thrd.WaitFor, в то время как метод THICOMEX.ExecuteRd в параллельном потоке получает сигнал не остановки, а приёма данных и блокируется на вызове Sender.Synchronize(SyncExecRd).
Можно уменьшить вероятность этого, если первым в массиве на WaitForMultipleObjects поставить EvRdStop. Тогда при одновременном срабатывании сигналов, условие if (Signaled = WAIT_OBJECT_0{=0}) then break сработает. Но: при активном приёме данных остаётся вероятность, что COMEX.doStop будет вызвано ПОСЛЕ if (Signaled = WAIT_OBJECT_0) then break, но перед Sender.Synchronize(SyncExecRd), что опять же приведёт к deadlock.
Попробую симулировать такую ситуацию, если будет время.

Редактировалось 2 раз(а), последний 2017-05-17 12:18:26
карма: 26

0
Ответов: 9906
Рейтинг: 351
#43: 2017-05-18 10:37:29 ЛС | профиль | цитата
Netspirit писал(а):
Но: при активном приёме данных остаётся вероятность, что COMEX.doStop будет вызвано ПОСЛЕ if (Signaled = WAIT_OBJECT_0) then break, но перед Sender.Synchronize(SyncExecRd), что опять же приведёт к deadlock.

Предположительно исхожу из того, что имелся таки в виду COMEX.doClose, а не COMEX.doStop
Далее, написанное Вами не возможно, если приоритет потоков ожидания выше, чем у основного потока.
Кажется, он у нас THREAD_PRIORITY_HIGHEST
Аналогично, и в случае "а если оба" -- так не бывает. Если мы делаем SetEvent из основного потока, то потоки ожидания именно ожидают. Что означает отсутствие событий приема. И следующие за SetEvent коды будут исполняться только после полного и безусловного завершения потока (приоритет!)

Однако соглашусь, обрабатывать события приема, при установленном сигнале останова - неправильно.
В этом случае "жульство" не катит, надо делать честные массивы. В смысле - не настолько это утяжеляет коды, чтобы ради этого бодаться.
Ну тогда вот он код, без "жульства".
(файл заменен - см. ниже)

Netspirit, я специально не изучал пока вопросы возможного "кольцевания".
Не потому, что я против этого. А потому, чтобы не путаться.
Можно еще и обработку ошибок приклеить (всякие там ParityEroor, FrameError, и т.п.).
Можно еще вспомнить про Ваш Synchronize, DeferredEvent от nesco - и начать бурно протестовать против "индуизма" кодинга (Вы используете AM_SYNC_METHOD, nesco - WM_DEFERREDEVENT, а Кладов - CM_EXECPROC)

Можно все это обсуждать (да и находить адекватные решения), но не все сразу. А то дурдом получится

Редактировалось 5 раз(а), последний 2017-05-18 11:58:43
карма: 9

0
Ответов: 4612
Рейтинг: 746
#44: 2017-05-18 11:24:39 ЛС | профиль | цитата
Вроде выглядит неплохо. Попробую как-нибудь протестировать.

По поводу "кольцевания". Пока что столкнулся с тем, что при использовании критической секции для "последовательной выдачи" событий компонента (чтобы события с разных потоков в одном компоненте не происходили одновременно), получается deadlock при такой последовательности:
- параллельный поток захватывает секцию и вызывает событие
- в этот момент главный поток вызывает метод, который должен выдать событие, использующее ту же секцию, и становится на ожидание её освобождения
- а событие параллельного потока внутри секции обращается к какому-нибудь визуальному компоненту, который посылая сообщение в главный поток, блокирует параллельный поток.
Оба потока ожидают освобождения друг друга.
Подумываю, что решением для "последовательной выдачи событий" будет некая "очередь событий", когда главный поток при вызове методов никак не блокируется внутри компонента, а просто помещает требование вызвать событие в эту очередь. Что-то типа DeferredEvent, но внутри компонента, без использования сообщений Windows.

Редактировалось 2 раз(а), последний 2017-05-18 11:27:41
карма: 26

0
Ответов: 9906
Рейтинг: 351
#45: 2017-05-18 11:48:08 ЛС | профиль | цитата
Да все правильно ты пишешь ...
Однако есть у нас тонкость с повышенным приоритетом (что правильно - чем ниже процент занятости потока, тем выше должен быть приоритет), которая делает ситуацию не настолько страшной.

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

--- Добавлено в 2017-05-18 11:57:22

Блин, попутал коды выхода из ожидания - обновил, а то неприлично, как-то

Редактировалось 3 раз(а), последний 2017-05-18 16:43:08
карма: 9

0
файлы: 1hiCOMEX.rar [2.7KB] [533]
Сообщение
...
Прикрепленные файлы
(файлы не залиты)