Для чего нужен TMonitor в системном блоке Delphi? - PullRequest
31 голосов
/ 31 июля 2010

После прочтения статей "Simmering Unicode, доведите DPL до кипения" и "Simmering Unicode, доведите DPL до кипения (часть 2)" из "Oracle в Delphi" "(Аллен Бауэр), Oracle - это все, что я понимаю:

В статье упоминается Delphi Parallel Library (DPL), блокировка свободных структур данных, взаимные исключения блокировки и переменные условия (эта статья в Википедии переходит к ' Monitor (синхронизация) ', а затем вводит новый тип записи TMonitor для синхронизации потоков и описывает некоторые его методы.

Существуют ли вводные статьи с примерами, показывающими, когда и как можно использовать этот тип записей Delphi? В сети есть документация .

  • В чем основное отличие TCriticalSection от TMonitor?

  • Что я могу сделать с помощью методов Pulse и PulseAll?

  • Имеет ли он аналог, например, в C # или языке Java?

  • Есть ли какой-либо код в RTL или VCL, который использует этот тип (так что он может служить примером)?


Обновление: статья Почему размер TObject удвоился в Delphi 2009? объясняет, что каждый объект в Delphi теперь можно заблокировать с помощью записи TMonitor по цене четырех дополнительных байтов на экземпляр.

Похоже, что TMonitor реализован аналогично Внутренним замкам в языке Java :

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

Wait , Pulse и PulseAll в Delphi кажутся аналогами wait () , notify () и notifyAll () на языке программирования Java. Поправь меня, если я ошибаюсь:)


Обновление 2: Пример кода для приложения Producer / Consumer с использованием TMonitor.Wait и TMonitor.PulseAll, основанный на статье о защищенных методах в учебниках по Java (tm) ( комментарии приветствуются):

Это приложение для обмена данными между двумя нитями: производитель, который создает данные, и потребитель, который что-то делает с этим. Два потока общаются с помощью общий объект. Координация необходимо: потребительский поток должен не пытаться получить данные до того, как поток доставил его, и продюсер темы не должен пытаться доставить новые данные если потребитель не получил старые данные.

В этом примере данные представляют собой серию текстовых сообщений, которые передаются через объект типа Drop:

program TMonitorTest;

// based on example code at http://download.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  Drop = class(TObject)
  private
    // Message sent from producer to consumer.
    Msg: string;
    // True if consumer should wait for producer to send message, false
    // if producer should wait for consumer to retrieve message.
    Empty: Boolean;
  public
    constructor Create;
    function Take: string;
    procedure Put(AMessage: string);
  end;

  Producer = class(TThread)
  private
    FDrop: Drop;
  public
    constructor Create(ADrop: Drop);
    procedure Execute; override;
  end;

  Consumer = class(TThread)
  private
    FDrop: Drop;
  public
    constructor Create(ADrop: Drop);
    procedure Execute; override;
  end;

{ Drop }

constructor Drop.Create;
begin
  Empty := True;
end;

function Drop.Take: string;
begin
  TMonitor.Enter(Self);
  try
    // Wait until message is available.
    while Empty do
    begin
      TMonitor.Wait(Self, INFINITE);
    end;
    // Toggle status.
    Empty := True;
    // Notify producer that status has changed.
    TMonitor.PulseAll(Self);
    Result := Msg;
  finally
    TMonitor.Exit(Self);
  end;
end;

procedure Drop.Put(AMessage: string);
begin
  TMonitor.Enter(Self);
  try
    // Wait until message has been retrieved.
    while not Empty do
    begin
      TMonitor.Wait(Self, INFINITE);
    end;
    // Toggle status.
    Empty := False;
    // Store message.
    Msg := AMessage;
    // Notify consumer that status has changed.
    TMonitor.PulseAll(Self);
  finally
    TMonitor.Exit(Self);
  end;
end;

{ Producer }

constructor Producer.Create(ADrop: Drop);
begin
  FDrop := ADrop;
  inherited Create(False);
end;

procedure Producer.Execute;
var
  Msgs: array of string;
  I: Integer;
begin
  SetLength(Msgs, 4);
  Msgs[0] := 'Mares eat oats';
  Msgs[1] := 'Does eat oats';
  Msgs[2] := 'Little lambs eat ivy';
  Msgs[3] := 'A kid will eat ivy too';
  for I := 0 to Length(Msgs) - 1 do
  begin
    FDrop.Put(Msgs[I]);
    Sleep(Random(5000));
  end;
  FDrop.Put('DONE');
end;

{ Consumer }

constructor Consumer.Create(ADrop: Drop);
begin
  FDrop := ADrop;
  inherited Create(False);
end;

procedure Consumer.Execute;
var
  Msg: string;
begin
  repeat
    Msg := FDrop.Take;
    WriteLn('Received: ' + Msg);
    Sleep(Random(5000));
  until Msg = 'DONE';
end;

var
  ADrop: Drop;
begin
  Randomize;
  ADrop := Drop.Create;
  Producer.Create(ADrop);
  Consumer.Create(ADrop);
  ReadLn;
end.

Теперь это работает, как и ожидалось, однако есть деталь, которую я мог бы улучшить: вместо блокировки всего экземпляра Drop с помощью TMonitor.Enter(Self);, я мог бы выбрать подход с мелкозернистой блокировкой с (приватным) полем "FLock". , используя его только в методах Put и Take по TMonitor.Enter(FLock);.

Если я сравниваю код с версией Java, я также замечаю, что в Delphi нет InterruptedException, который можно использовать для отмены вызова Sleep.

Обновление 3 : в мае 2011 года запись в блоге о OmniThreadLibrary представила возможную ошибку в реализации TMonitor. Похоже, это связано с записью в Quality Central . В комментариях упоминается, что патч был предоставлен пользователем Delphi, но он не виден.

Обновление 4 : запись в блоге в 2013 году показала, что, хотя TMonitor «честен», его производительность хуже, чем в критической секции.

1 Ответ

8 голосов
/ 31 июля 2010

TMonitor сочетает в себе понятие критической секции (или простого мьютекса) вместе с условной переменной.Вы можете прочитать о том, что такое «монитор» здесь: http://en.wikipedia.org/wiki/Monitor_%28synchronization%29.

В любом месте, где бы вы ни использовали критическую секцию, вы можете использовать монитор.Вместо объявления TCriticalSection вы можете просто создать экземпляр TObject и затем использовать его.

TMonitor.Enter(FLock);
try
  // protected code
finally
  TMonitor.Exit(FLock);
end;

Где FLock - любой экземпляр объекта.Обычно я просто создаю объект TObject:

FLock := TObject.Create;
...