Вверх ↑
Ответов: 4612
Рейтинг: 746
#1: 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, хотя я пока обходился).
карма: 26

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