2010-07-31 20 views
28

Después de leer los artículos "Simmering Unicode, bring DPL to a boil" y "Simmering Unicode, bring DPL to a boil (Part 2)" de "El oráculo de Delfos" (Allen Bauer), Oracle es todo lo que entiendo :)¿Para qué sirve TMonitor en la unidad Delphi System?

El artículo menciona Delphi Parallel Library (DPL), bloqueo de las estructuras de datos libres, mutual exclusion locks y condition variables (esto artículo de Wikipedia hacia delante a 'Monitor (synchronization)', y luego introduce el nuevo TMonitor record type para la sincronización de hilos y describe algunos de sus métodos.

¿Hay artículos de introducción con ejemplos que muestran cuándo y cómo se puede utilizar este tipo de registro Delphi? No es un documentation en línea.

  • ¿Cuál es la principal diferencia entre TCriticalSection y TMonitor?

  • ¿Qué puedo hacer con los métodos Pulse y PulseAll?

  • ¿Tiene una contrapartida, por ejemplo, en C# o en el lenguaje Java?

  • ¿Hay algún código en el RTL o el VCL que utiliza este tipo (por lo que podría servir como un ejemplo)?


Actualización: el artículo Why Has the Size of TObject Doubled In Delphi 2009? explica que todos los objetos de Delphi ahora se puede bloquear mediante un registro TMonitor, al precio de cuatro bytes adicionales por cada instancia.

Parece que TMonitor está implementado similar a Intrinsic Locks in the Java language:

Cada objeto tiene un bloqueo intrínseco asociada a ella. Por convención, un hilo que necesita acceso exclusivo y coherente a campos de un objeto tiene que adquirir bloqueo intrínseca del objeto antes de acceder a ellos, y luego liberar el bloqueo intrínseco cuando se hace con ellos.

Wait, Pulse y PulseAll en Delphi parecen ser homólogos de wait(), notify() y notifyAll() en el lenguaje de programación Java. corrígeme si estoy equivocado :)


Actualización 2: Código de ejemplo para una aplicación productor/consumidor usando TMonitor.Wait y TMonitor.PulseAll, basado en un artículo acerca de los métodos guardados en los Java(tm) tutorials (comentarios son bienvenidos):

Este tipo de acciones de aplicaciones de datos entre dos hilos: el productor, que crea los datos, y la de consumo, que hace algo con él. Los dos hilos se comunican utilizando un objeto compartido .La coordinación es esencial : el hilo consumidor debe No intente recuperar los datos antes de que el hilo productor ha entregado, y el hilo productor no debe intentar ofrecer nuevos datos si el consumidor no ha recuperado los datos antiguos .

En este ejemplo, los datos son una serie de mensajes de texto, los cuales son compartidos a través de un objeto de tipo 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. 

Ahora bien, esto funciona como se espera, sin embargo, hay un detalle que pude mejorar: en lugar de bloquear toda la instancia de Drop con TMonitor.Enter(Self);, podría elegir un enfoque de bloqueo de grano fino, con un campo (privado) "FLock", utilizándolo solo en los métodos Put y Take por TMonitor.Enter(FLock);.

Si comparo el código con la versión de Java, también noto que no hay InterruptedException en Delphi que se puede utilizar para cancelar una llamada de Sleep.

Actualización 3: en mayo de 2011, un blog entry sobre OmniThreadLibrary presentó un posible error en la implementación de TMonitor. Parece estar relacionado con una entrada en Quality Central. Los comentarios mencionan que un parche ha sido provisto por un usuario de Delphi, pero no es visible.

Update 4: Un blog post en 2013 mostraron que mientras TMonitor es 'justo', su rendimiento es peor que la de una sección crítica.

+0

'TMonitor' tenía errores graves, que finalmente fueron corregidos en XE2 UPD 4. Los errores pueden ser manifestadas por el uso TMonitor en' TThreadedQueue'. Consulte ['TThreadedQueue no es apto para múltiples consumidores?'] (Http://stackoverflow.com/q/4856306/576719) para obtener más información. –

Respuesta

4

TMonitor combina la noción de una sección crítica (o un mutex simple) junto con una variable de condición. Puede leer sobre qué es un "monitor" aquí: http://en.wikipedia.org/wiki/Monitor_%28synchronization%29.

En cualquier lugar donde utilice una sección crítica, puede usar un monitor. En lugar de declarar una TCriticalSection, puedes crear una instancia de TObject y luego usarla.

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

Donde FLock es cualquier instancia de objeto. Normalmente, sólo se crea un TObject:

FLock := TObject.Create; 
+0

¿Entonces este 'FLock' es el monitor en este ejemplo? (el "objeto destinado a ser utilizado con seguridad por más de un hilo" como se describe en Wikipedia) – mjn

+0

FLock es cualquier instancia de objeto. Puede ser simplemente una simple instancia TObject. FLock: = TObject.Create; –

+5

Todavía no es suficiente. Usted ha mostrado cómo emular una sección crítica con TMonitor, pero seguro que ese no es el problema real para el que TMonitor fue diseñado. ¿Puedes dar un ejemplo de código más interesante? – kludg