После прочтения статей "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 «честен», его производительность хуже, чем в критической секции.