Delphi 系统单元中的 TMonitor 有啥用?

Posted

技术标签:

【中文标题】Delphi 系统单元中的 TMonitor 有啥用?【英文标题】:What is TMonitor in Delphi System unit good for?Delphi 系统单元中的 TMonitor 有什么用? 【发布时间】:2011-03-23 14:21:01 【问题描述】:

读完“The Oracle at Delphi”(Allen Bauer)的文章"Simmering Unicode, bring DPL to a boil" 和"Simmering Unicode, bring DPL to a boil (Part 2)" 后,我只了解Oracle :)

文章提到了Delphi并行库(DPL),无锁数据结构,mutual exclusion locks和condition variables(这篇***文章转发到'Monitor (synchronization)',然后介绍了用于线程同步的新TMonitor record type并描述了它的一些方法。

是否有介绍文章以及说明何时以及如何使用这种 Delphi 记录类型的示例?网上有一些documentation。

TCriticalSection 和 TMonitor 的主要区别是什么?

我可以用PulsePulseAll方法做什么?

它有对应的 C# 或 Java 语言吗?

RTL 或 VCL 中是否有任何代码使用这种类型(因此可以作为示例)?


更新:Why Has the Size of TObject Doubled In Delphi 2009? 文章解释说,现在可以使用 TMonitor 记录锁定 Delphi 中的每个对象,代价是每个实例增加四个字节。

看起来TMonitor的实现类似于Intrinsic Locks in the Java language:

每个对象都有一个内在锁 与之相关联。按照惯例,一个 需要独占的线程和 对对象的一致访问 字段必须获取对象的 访问它们之前的内在锁, 然后释放内在锁 完成后。

Wait、Pulse 和 PulseAll 在 Delphi 中似乎与 Java 编程语言中的 wait()、notify() 和 notifyAll() 对应。如果我错了,请纠正我:)


更新 2:生产者/消费者应用程序示例代码,使用 TMonitor.WaitTMonitor.PulseAll,基于 Java(tm) tutorials 中有关受保护方法的文章(欢迎 cmets):

这种应用程序共享数据 在两个线程之间:生产者, 创建数据,以及 消费者,这对它有作用。 两个线程使用 共享对象。协调是 必要的:消费者线程必须 不尝试检索数据 在生产者线程之前 交付它,生产者线程 不得尝试提供新数据 如果消费者没有取回 旧数据。

在本例中,数据是一系列文本消息,通过 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.

现在这按预期工作了,但是有一个细节我可以改进:我可以选择一个细粒度的锁定方法,而不是使用TMonitor.Enter(Self); 锁定整个 Drop 实例,使用(私有)“FLock”字段,仅在 TMonitor.Enter(FLock); 的 Put 和 Take 方法中使用它。

如果我将代码与Java版本进行比较,我还注意到Delphi中没有InterruptedException可以用来取消对Sleep的调用。

更新 3:2011 年 5 月,关于 OmniThreadLibrary 的blog entry 提出了 TMonitor 实现中可能存在的错误。它似乎与Quality Central 中的条目有关。 cmets 提到了 Delphi 用户提供了一个补丁,但它是不可见的。

更新 4:2013 年的blog post 表明,虽然 TMonitor 是“公平的”,但其性能比临界区差。

【问题讨论】:

TMonitor 有严重的错误,最终在 XE2 upd 4 中得到纠正。错误可以通过在TThreadedQueue 中使用 TMonitor 来体现。请参阅TThreadedQueue not capable of multiple consumers? 了解更多信息。 【参考方案1】:

TMonitor 将临界区(或简单互斥体)的概念与条件变量结合在一起。您可以在此处了解什么是“监视器”:http://en.wikipedia.org/wiki/Monitor_%28synchronization%29。

任何你会使用临界区的地方,你都可以使用监视器。您可以简单地创建一个 TObject 实例然后使用它,而不是声明一个 TCriticalSection。

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

其中 FLock 是任何对象实例。通常,我只是创建一个 TObject:

FLock := TObject.Create;

【讨论】:

FLock 是任何对象实例。它可以只是一个简单的 TObject 实例。 FLock := TObject.Create; 还是不够。您已经展示了如何使用 TMonitor 模拟关键部分,但确定这不是 TMonitor 设计的真正问题。能举个更有趣的代码例子吗? 您可能还想添加一些信息,为什么您选择向 VCL 添加通常被谴责的锁定任何对象的能力。参见例如***.com/questions/251391/why-is-lockthis-bad。 Mjustin,我不会说FLock 这个例子中的监视器。它一个监视器,我们用它来保护一些代码。如果我们已经有其他一些 TObject,我们可能会使用它的监视器而不是为此唯一目的创建一个新对象。它不一定是被多个线程使用的对象——这是在try-finally部分中使用的任何东西——但要获得任何好处,其他线程需要知道FLock这样他们就可以等待同一个对象。 (就像临界区一样,如果只有一个线程使用它是没有用的。) @Rob:***将 Monitor 定义为“一个旨在被多个线程安全使用的对象”——在示例中,一个锁定对象 (FLock) 用于保护不同对象中的代码。文章 mghie 链接说在对象上创建全局锁是个坏主意(“为什么 lock(this) ... 不好?”)。其他线程不需要“知道”(有权访问)FLock,FLock 可以是私有实例变量,只在一个同步方法中使用。

以上是关于Delphi 系统单元中的 TMonitor 有啥用?的主要内容,如果未能解决你的问题,请参考以下文章

delphi里application.MessageBox()与showmessage()有啥区别?

CheckSynchronize实现的不必要的复杂

Delphi XE 中的多线程有啥新功能?

操作系统中的页面和块有啥区别?

delphi中的各类文件类型(转)

delphi中的各种文件类型介绍