Seth спросил (перевод оригинального поста без комментариев), как может он выполнить корректную очистку ресурсов при возбуждении исключения, если он не знает, надо ли ему освобождать критическую секцию.
Я использую SEH (исключения - прим. пер.) и у меня есть несколько блоков try/except, в которых код входит и покидает критические секции. Если возникает исключение, я не знаю, владею ли я сейчас критической секцией или нет. Даже обёртка кода в try/finally не решает мои проблемы.
Ответ: вы знаете, владеете ли вы критической секцией, потому что вы сами вошли в неё.
Метод 1: сделать вывод из строки кода.
"Если я сейчас в этой строке кода, то я должен быть внутри критической секции".
try ... EnterCriticalSection(X); try ... // если исключение возникнет на этом участке, ... finally // ...то убедимся, что мы вышли из критической секции LeaveCriticalSection(X); end; ... except ... end;Заметим, что эта техника устойчива к вложенным вызовам EnterCriticalSection. Если вы собираетесь войти в критическую секцию ещё раз, тогда просто оберните вложенный вызов в собственный блок try/finally.
Метод 2: сделать вывод из локального состояния.
"Я запомню, входил ли я в критическую секцию".
var Entered: Integer; ... Entered := 0; try ... EnterCriticalSection(X); Inc(Entered); ... Dec(Entered); LeaveCriticalSection(X); ... except while Entered > 0 do begin LeaveCriticalSection(X); Dec(Entered); end; ... end;Заметим, что эта техника также устойчива к вложенным вызовам EnterCriticalSection. Если вы хотите занять критическую секцию ещё раз, то просто увеличьте Entered ещё раз.
Метод 3: отслеживать объектом.
Оберните TCriticalSection в другой объект.
Это наиболее точно передаёт то, что Seth делает сейчас.
type TMyCriticalSection = class(TSynchroObject) private FOwner: Cardinal; FDepth: Integer; function GetOwned: Boolean; protected FSection: TRTLCriticalSection; public constructor Create; destructor Destroy; override; procedure Acquire; override; procedure Release; override; function TryEnter: Boolean; procedure Enter; inline; procedure Leave; inline; property Owned: Boolean read GetOwned; end; { TMyCriticalSection } constructor TMyCriticalSection.Create; begin inherited Create; FSection.Initialize; end; destructor TMyCriticalSection.Destroy; begin FSection.Free; inherited Destroy; end; procedure TMyCriticalSection.Acquire; begin FSection.Enter; FOwner := GetCurrentThreadId; Inc(FDepth); end; procedure TMyCriticalSection.Release; begin Dec(FDepth); if FDepth = 0 then FOwner := 0; FSection.Leave; end; function TMyCriticalSection.TryEnter: Boolean; begin Result := FSection.TryEnter; if Result then begin FOwner := GetCurrentThreadId; Inc(FDepth); end; end; procedure TMyCriticalSection.Enter; begin Acquire; end; procedure TMyCriticalSection.Leave; begin Release; end; function TMyCriticalSection.GetOwned: Boolean; begin Result := (FOwner = GetCurrentThreadId); end; ... try Assert(not CS.Owned); ... CS.Enter; ... CS.Leave; ... except if CS.Owned then CS.Leave; end;Заметим, что этот код не устойчив к повторной входимости (и, соответственно, код Seth-а тоже). Если вы войдёте в критическую секцию дважды, то обработчик исключения выйдет из неё только 1 раз.
Также заметим, что мы проверяем, что критическая секция уже не занята нами до входа в этот блок кода. В противном случае наш код может освободить критическую секцию, которой он не владел (исключение после Assert, но до CS.Enter).
Метод 4: отслеживать умным объектом.
Оберните TCriticalSection в умный объект.
Добавьте такой private-метод с public-свойством к предыдущему классу:
function TMyCriticalSection.GetDepth: Integer; begin if Owned then Result := FDepth else Result := 0; end;Теперь вы можете корректно освобождать вложенные входы в критическую секцию:
var Depth: Integer; ... Depth := CS.Depth; try ... CS.Enter; ... CS.Leave; ... except while CS.Depth > Depth do CS.Leave; end;
Замечу, что я вообще скептически отношусь к изначальному вопросу.
Очистка после исключения, возбуждённого в коде, владеющим критической секцией, поднимает вопрос: "как вы узнаете, что именно безопасно очищать?". Вы завели критическую секцию для работы с какими-то данными. Критическая секция защищает ваши данные, пока вы изменяете их, т.е. переводите их в некоторое нестабильное состояние и не хотите, чтобы другие видели эти данные в такой несогласованной форме. Но если у вас появляется исключение во время работы внутри критической секции - ну, ваши данные в момент возбуждения исключения находятся в некорректном состоянии. Простой выход из критической секции оставит ваши данные в недопустимом состоянии, которое может привести к более трудно диагностируемым проблемам потом: "как это мой счётчик сумел сбиться?".
В одном из будущих постов я выскажу ещё претензии к исключениям.
Упражнение: почему нам не нужно использовать синхронизацию для защиты использования FDepth и FOwner?
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.