2010-07-31 2 views
28

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

+0

'TMonitor' были серьезные ошибки, которые были окончательно устранены в XE2 UPD 4. Ошибки могут быть проявленные при использовании TMonitor в' TThreadedQueue'. См. ['TThreadedQueue, не способный к нескольким потребителям?] (Http://stackoverflow.com/q/4856306/576719) для получения дополнительной информации. –

ответ

4

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; 
+0

Итак, этот 'FLock' является монитором в этом примере? («объект, предназначенный для безопасного использования более чем одним потоком», как описано в Википедии) – mjn

+0

FLock - это любой экземпляр объекта. Это может быть просто простой экземпляр TObject. FLock: = TObject.Create; –

+5

Все еще не хватает. Вы показали, как подражать критическому разделу с TMonitor, но уверен, что это не настоящая проблема, для которой был разработан TMonitor. Можете ли вы привести более интересный пример кода? – kludg