阅读文章“酝酿Unicode,带上DPL煮沸” 和 "沸腾 Unicode,让 DPL 沸腾(第 2 部分)”《The Oracle at Delphi》(Allen Bauer)的,Oracle 就是我所理解的:)
文章中提到了 Delphi 并行库(DPL)、无锁数据结构、互斥锁和条件变量(此维基百科文章转发至 '监视器(同步)',然后介绍新的 TMonitor用于线程同步的记录类型并描述了它的一些方法。
是否有带有示例的介绍文章显示何时以及如何使用此 Delphi 记录类型?在线有一些文档。
-
TCriticalSection 和 TMonitor 之间的主要区别是什么?
-
我可以使用 Pulse
和 PulseAll
方法做什么?
-
它是否有对应的语言,例如 C# 或 Java 语言?
-
RTL 或 VCL 中是否有使用此类型的代码(因此可以作为示例)?
更新:文章 为什么在 Delphi 2009 中 TObject 的大小增加了一倍? 解释了现在 Delphi 中的每个对象都可以使用 TMonitor 记录来锁定,但代价是每个实例额外增加 4 个字节。
看起来 TMonitor 的实现类似于 Java 语言中的 内在锁:
每个对象都有一个内在锁
与之相关。按照惯例,一个
需要独占的线程
对对象的一致访问
fields 必须获取对象的
访问它们之前的内在锁,
然后释放内在锁
当他们完成时。
等待,脉冲 和 PulseAll 似乎是 wait(), notify() 和 notifyAll()。如果我错了,请纠正我:)
更新 2:使用 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”字段,仅在 TMonitor.Enter(FLock) 的 Put 和 Take 方法中使用它);
。
如果我将代码与 Java 版本进行比较,我还注意到 Delphi 中没有可用于取消 Sleep
调用的 InterruptedException
。
更新 3:2011 年 5 月,博客条目< /a> 关于 OmniThreadLibrary 提出了 TMonitor 实现中可能存在的错误。它似乎与 Quality Central 中的条目相关。评论提到 Delphi 用户已经提供了一个补丁,但它不可见。
更新 4:博客文章 2013 年的研究表明,虽然 TMonitor 是“一般”,但其性能比关键部分差。
After reading the articles "Simmering Unicode, bring DPL to a boil" and "Simmering Unicode, bring DPL to a boil (Part 2)" of "The Oracle at Delphi" (Allen Bauer), Oracle is all I understand :)
The article mentions Delphi Parallel Library (DPL), lock free data structures, mutual exclusion locks and condition variables (this Wikipedia article forwards to 'Monitor (synchronization)', and then introduces the new TMonitor record type for thread synchronization and describes some of its methods.
Are there introduction articles with examples which show when and how this Delphi record type can be used? There is some documentation online.
-
What is the main difference between TCriticalSection and TMonitor?
-
What can I do with the Pulse
and PulseAll
methods?
-
Does it have a counterpart for example in C# or the Java language?
-
Is there any code in the RTL or the VCL which uses this type (so it could serve as an example)?
Update: the article Why Has the Size of TObject Doubled In Delphi 2009? explains that every object in Delphi now can be locked using a TMonitor record, at the price of four extra bytes per instance.
It looks like TMonitor is implemented similar to Intrinsic Locks in the Java language:
Every object has an intrinsic lock
associated with it. By convention, a
thread that needs exclusive and
consistent access to an object's
fields has to acquire the object's
intrinsic lock before accessing them,
and then release the intrinsic lock
when it's done with them.
Wait, Pulse and PulseAll in Delphi seem to be counterparts of wait(), notify() and notifyAll() in the Java programming language. Correct me if I am wrong :)
Update 2: Example code for a Producer/Consumer application using TMonitor.Wait
and TMonitor.PulseAll
, based on an article about guarded methods in the Java(tm) tutorials (comments are welcome):
This kind of application shares data
between two threads: the producer,
that creates the data, and the
consumer, that does something with it.
The two threads communicate using a
shared object. Coordination is
essential: the consumer thread must
not attempt to retrieve the data
before the producer thread has
delivered it, and the producer thread
must not attempt to deliver new data
if the consumer hasn't retrieved the
old data.
In this example, the data is a series of text messages, which are shared through an object of type 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.
Now this works as expected, however there is a detail which I could improve: instead of locking the whole Drop instance with TMonitor.Enter(Self);
, I could choose a fine-grained locking approach, with a (private) "FLock" field, using it only in the Put and Take methods by TMonitor.Enter(FLock);
.
If I compare the code with the Java version, I also notice that there is no InterruptedException
in Delphi which can be used to cancel a call of Sleep
.
Update 3: in May 2011, a blog entry about the OmniThreadLibrary presented a possible bug in the TMonitor implementation. It seems to be related to an entry in Quality Central. The comments mention a patch has been provided by a Delphi user, but it is not visible.
Update 4: A blog post in 2013 showed that while TMonitor is 'fair', its performance is worse than that of a critical section.
发布评论
评论(1)
TMonitor 将临界区(或简单互斥体)的概念与条件变量结合起来。您可以在这里了解什么是“监视器”:
http://en.wikipedia。 org/wiki/Monitor_%28synchronization%29
任何需要使用临界区的地方,都可以使用监视器。您可以简单地创建一个
TObject
实例,然后使用它,而不是声明TCriticalSection
:其中
FLock
是任何对象实例。通常,我只是创建一个TObject
:TMonitor combines the notion of a critical section (or a simple mutex) along with a condition variable. You can read about what a "monitor" is here:
http://en.wikipedia.org/wiki/Monitor_%28synchronization%29
Any place you would use a critical section, you can use a monitor. Instead of declaring a
TCriticalSection
, you can simple create aTObject
instance and then use that:Where
FLock
is any object instance. Normally, I just create aTObject
: