В данной статье мы рассмотрим проблему, с которой столкнулись разработчики при работе с многопоточными приложениями Windows Service, использующими технологии Delphi и Pascal. Проблема связана с использованием кэша событий (EventCache) в модуле SysUtils, что приводит к ошибкам в синхронизации потоков.
Описание проблемы
В многопоточном приложении Windows Service, использующем более 30 потоков, была обнаружена проблема, связанная с функцией NewWaitObj в модуле SysUtils.EventCache. Функция NewWaitObj предназначена для создания объекта ожидания, который используется в методах синхронизации TMonitor для ожидания сигнала события. Однако, в условиях многопоточности, функция NewWaitObj иногда возвращает NIL вместо ожидаемого объекта события, что приводит к сбою работы TMonitor.Wait. Это, в свою очередь, влияет на корректность работы различных источников синхронизации потоков во VCL и RTL, а также может вызвать различные побочные проблемы в многопоточных приложениях.
Пример кода, вызывающего проблему
function NewWaitObj: Pointer;
var
EventItem: PEventItemHolder;
begin
EventItem := Pop(EventCache);
if EventItem <> nil then
begin
Result := EventItem.Event;
EventItem.Event := nil;
Push(EventItemHolders, EventItem);
end else
Result := NewSyncWaitObj;
ResetSyncWaitObj(Result);
end;
Условия возникновения проблемы
Проблема заключается в том, что функция Pop не должным образом защищена от гонок в условиях высокой нагрузки и многопоточности. В результате, один и тот же экземпляр EventItem может быть возвращен нескольким потокам одновременно, что приводит к условиям гонки внутри NewWaitObj. Один из потоков может получить указатель на событие и обнулить его, в то время как другой поток, получивший тот же указатель, уже будет ссылаться на неинициализированное значение.
Пример кода функции Pop
function Pop(var Stack: PEventItemHolder): PEventItemHolder;
begin
repeat
Result := Stack;
if Result = nil then
Exit;
until AtomicCmpExchange(Pointer(Stack), Result.Next, Result) = Result;
end;
Подтвержденное решение
Разработчики столкнулись с проблемой, которая проявляется не сразу и требует значительного количества времени и потоков для воспроизведения. Однако, было замечено, что после возникновения проблемы приложение продолжает работать с ошибками до перезапуска. Предложено решение - замена стандартной реализации NewWaitObj на свою, которая будет повторно вызывать оригинальную функцию до тех пор, пока не будет получен валидный объект.
Альтернативное решение
Одно из альтернативных решений - использование критической секции для синхронизации вызова функций NewWaitObj и FreeWaitObject. Это позволяет избежать условий гонки и обеспечивает корректную работу с объектами ожидания. Пример кода для инициализации альтернативной реализации MonitorSupport:
var
MonitorSupportFix: TMonitorSupport;
OldMonitorSupport: PMonitorSupport;
NewWaitObjCS: TCriticalSection;
function NewWaitObjFix: Pointer;
begin
if Assigned(NewWaitObjCS) then
NewWaitObjCS.Enter;
try
Result := OldMonitorSupport.NewWaitObject;
finally
if Assigned(NewWaitObjCS) then
NewWaitObjCS.Leave;
end;
end;
procedure FreeWaitObjFix(WaitObject: Pointer);
begin
if Assigned(NewWaitObjCS) then
NewWaitObjCS.Enter;
try
OldMonitorSupport.FreeWaitObject(WaitObject);
finally
if Assigned(NewWaitObjCS) then
NewWaitObjCS.Leave;
end;
end;
procedure InitMonitorSupportFix;
begin
OldMonitorSupport := System.MonitorSupport;
MonitorSupportFix := OldMonitorSupport^;
MonitorSupportFix.NewWaitObject := NewWaitObjFix;
MonitorSupportFix.FreeWaitObject := FreeWaitObjFix;
System.MonitorSupport := @MonitorSupportFix;
end;
initialization
NewWaitObjCS := TCriticalSection.Create;
InitMonitorSupportFix;
finalization
FreeAndNil(NewWaitObjCS);
end.
Заключение
Проблема многопоточности в Windows Service приложениях на Delphi является сложной, но решаемой. Важно понимать, как работают механизмы синхронизации и как они могут вести себя в условиях высокой нагрузки. Представленные решения могут помочь разработчикам устранить проблемы с синхронизацией потоков и обеспечить стабильную работу многопоточных приложений.
Проблема описана для разработчиков, связанная с ошибками в многопоточном программировании Windows Service, где использование модуля SysUtils.EventCache в Delphi и Pascal может привести к сбоям из-за неправильной работы с объектами синхронизации при высок
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.