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 можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.