Шаг 297.
Модели потоков. Дополнительные механизмы синхронизации. Критические секции

    На этом шаге мы дадим определение критической секции и рассмотрим небольшой фрагмент примера ее использования.

    Критические секции (critical sections) - это механизм, предназначенный для синхронизации потоков внутри одного процесса. Как и мьютекс, критическая секция может в один момент времени принадлежать только одному потоку, однако она представляет собой более быстрый и эффективный механизм, чем мьютексы. Перед использованием критической секции необходимо инициализировать ее функцией InitializeCriticalSection:

procedure InitializeCriticalSection(
  var lpCriticalSection: TRTLCriticalSection 
); stdcall;

    После создания объекта поток перед доступом к защищаемому ресурсу должен вызвать функцию EnterCriticalSection:

procedure EnterCriticalSection(
  var lpCriticalSection: TRTLCriticalSection 
); stdcall;

    Если в этот момент ни один из потоков в процессе не владеет объектом, то поток становится владельцем критической секции и продолжает выполнение. Если секция уже захвачена другим потоком, то выполнение потока, вызвавшего функцию, приостанавливается до ее освобождения.

    Поток, владеющий критической секцией, может повторно вызывать функцию EnterCriticalSection без блокирования своего исполнения. По завершении работы с защищаемым ресурсом поток должен вызвать функцию LeaveCriticalSection:

procedure LeaveCriticalSection(
  var lpCriticalSection: TRTLCriticalSection 
): stdcall;

    Эта функция освобождает объект независимо от количества предыдущих вызовов потоком функции EnterCriticalSection. Если имеются другие потоки, ожидающие освобождения секции, один из них становится ее владельцем и продолжает исполнение. Если поток завершился, не освободив критическую секцию, ее состояние становится неопределенным, что может вызвать блокировку работы программы.

    Можно попытаться захватить объект без замораживания потока. Для этого служит функция TryEnterCriticalSection:

function TryEnterCriticalSection(
  var lpCriticalSection: TRTLCriticalSection 
): BOOL; stdcall;

    Эта функция проверяет, захвачена ли секция в момент ее вызова. Если да - функция возвращает False, в противном случае - захватывает секцию и возвращает True.

    По завершении работы с критической секцией она должна быть уничтожена вызовом функции DeleteCriticalSection:

procedure DeleteCriticalSection(
  var lpCriticalSection: TRTLCriticalSection 
); stdcall;

    Рассмотрим пример приложения, осуществляющего в нескольких потоках загрузку данных по сети. Глобальные переменные BytesSummary и TimeSummary хранят общее количество загруженных байтов и время загрузки. Эти переменные каждый поток обновляет по мере считывания данных. Для предотвращения конфликтов приложение должно защитить общий ресурс при помощи критической секции.

var
  // Глобальные переменные
  CriticalSection: TRTLCriticalSection;
  BytesSummary: Cardinal;
  TimeSummary: TDateTime;
  AverageSpeed: Float;
.   .   .
// При инициализации приложения
InitializeCriticalSection(CriticalSection);
BytesSummary := 0;
TimeSummary := 0;
AverageSpeed := 0;

// В методе Execute потока, загружающего данные.
repeat
  BytesRead := ReadDataBlockFromNetwork;
  EnterCriticalSection(CriticalSection);
  try
    BytesSummary := BytesSummary + BytesRead;
    TimeSummary := TimeSummary + (Now - ThreadStartTime);
    if TimeSummary > 0 then
      AverageSpeed := BytesSummary / (TimeSummary/24/60/60);
  finally
    LeaveCriticalSection(CriticalSection)
  end;
until LoadComplete;

// При завершении приложения
DeleteCriticalSection(CriticalSection);

    Delphi предоставляет класс, объявленный в модуле SyncObjs.pas, инкапсулирующий функциональность критической секции:

type
  TCriticalSection = class(TSynchroObject) 
  public
    constructor Create;
    destructor Destroy; override;
    procedure Acquire; override;
    procedure Release; override;
    procedure Enter;
    procedure Leave; 
  end;

    Методы Enter и Leave являются синонимами методов Acquire и Release соответственно и добавлены для лучшей читаемости исходного кода:

procedure TCriticalSection.Enter; 
begin
  Acquire; 
end;
procedure TCriticalSection.Leave; 
begin
  Release; 
end;

    На следующем шаге мы рассмотрим защищенный доступ к переменным.




Предыдущий шаг Содержание Следующий шаг