После прочтения статьи "Simmering Unicode, bring DPL to a boil" и "Simmering Unicode, bring DPL to a boil (Part 2)" из "The Oracle на Delphi" (Allen Bauer), Oracle все, что я понимаю :)Что такое TMonitor в системном блоке Delphi?
В статье упоминается Delphi Parallel Library (DPL), блокировка свободных структур данных, mutual exclusion locks и condition variables (эта статья Википедии вперед к «Monitor (synchronization)», а затем вводит новый TMonitor record type для синхронизации потоков и описывает некоторые из его методов.
Есть ли введение статьи с примерами, которые показывают, когда и как можно использовать этот тип записи Delphi? там составляет documentation.
В чем основное отличие между TCriticalSection и TMonitor?
Что я могу сделать с методами
Pulse
иPulseAll
?Есть ли у него аналог, например, на языке C# или на языке Java?
Есть ли какой-либо код в RTL или VCL, который использует этот тип (чтобы он мог служить в качестве примера)?
Обновление: статья Why Has the Size of TObject Doubled In Delphi 2009? объясняет, что каждый объект в Delphi теперь может быть заблокирован с помощью записи TMonitor, по цене четырех дополнительных байт в экземпляре.
Похоже TMonitor реализован похож на Intrinsic Locks in the Java language:
Каждый объект имеет внутреннюю блокировку , связанный с ним. По соглашению, нить, которая нуждается в эксклюзивной и постоянный доступ к полям объекта перечисляются должен приобрести внутреннюю блокировку объекта перед доступом к ним, , а затем освободить внутреннюю блокировку , когда это делается с ними.
Wait, Pulse и PulseAll в Delphi, кажется, двойники wait(), notify() и notifyAll() на языке программирования Java. Поправьте меня, если я ошибаюсь :)
Update 2: Пример кода для Producer/Consumer приложения с использованием TMonitor.Wait
и TMonitor.PulseAll
, основываясь на статье о охранявших методов в Java(tm) tutorials (комментарии приветствуются):
Этот вид данных акций приложения между двумя потоками: продюсер, , что создает данные, и потребителей, что делает что-то с ним. Два потока взаимодействуют с использованием общего объекта .Координация важна: потребитель поток должен не пытаться получить данные перед производителем нить доставила его, и производитель резьбы не должен пытаться доставить новые данные , если потребитель не забрал старые данных ,
В этом примере, данные серии текстовых сообщений, которые являются общими с помощью объекта типа 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: A blog post в 2013 году показало, что, хотя TMonitor является «честным», его производительность хуже, чем у критической секции.
'TMonitor' были серьезные ошибки, которые были окончательно устранены в XE2 UPD 4. Ошибки могут быть проявленные при использовании TMonitor в' TThreadedQueue'. См. ['TThreadedQueue, не способный к нескольким потребителям?] (Http://stackoverflow.com/q/4856306/576719) для получения дополнительной информации. –