【发布时间】:2009-05-26 07:48:56
【问题描述】:
我有一个内存数据结构,它可以被多个线程读取,并且只能由一个线程写入。目前我正在使用一个关键部分来使这个访问线程安全。不幸的是,即使只有另一个读者正在访问它,这也会阻止读者。
有两种解决方法:
- 使用 TMultiReadExclusiveWriteSynchronizer
- 使用无锁方法消除任何阻塞
对于 2. 到目前为止,我得到了以下内容(任何无关紧要的代码都被省略了):
type
TDataManager = class
private
FAccessCount: integer;
FData: TDataClass;
public
procedure Read(out _Some: integer; out _Data: double);
procedure Write(_Some: integer; _Data: double);
end;
procedure TDataManager.Read(out _Some: integer; out _Data: double);
var
Data: TDAtaClass;
begin
InterlockedIncrement(FAccessCount);
try
// make sure we get both values from the same TDataClass instance
Data := FData;
// read the actual data
_Some := Data.Some;
_Data := Data.Data;
finally
InterlockedDecrement(FAccessCount);
end;
end;
procedure TDataManager.Write(_Some: integer; _Data: double);
var
NewData: TDataClass;
OldData: TDataClass;
ReaderCount: integer;
begin
NewData := TDataClass.Create(_Some, _Data);
InterlockedIncrement(FAccessCount);
OldData := TDataClass(InterlockedExchange(integer(FData), integer(NewData));
// now FData points to the new instance but there might still be
// readers that got the old one before we exchanged it.
ReaderCount := InterlockedDecrement(FAccessCount);
if ReaderCount = 0 then
// no active readers, so we can safely free the old instance
FreeAndNil(OldData)
else begin
/// here is the problem
end;
end;
不幸的是,OldData 实例被替换后会出现一个小问题。如果 Read 方法中当前没有其他线程(ReaderCount=0),则可以安全地处理它,仅此而已。但如果不是这样,我该怎么办? 我可以将它存储到下一次调用并在那里处理它,但是 Windows 调度理论上可以让读取器线程在 Read 方法中休眠并且仍然有对 OldData 的引用。
如果您发现上述代码有任何其他问题,请告诉我。这是要在多核的计算机上运行,并且上面的方法会被非常频繁地调用。
如果这很重要:我正在使用带有内置内存管理器的 Delphi 2007。我知道内存管理器在创建新类时可能会强制执行一些锁定,但我想暂时忽略它。
编辑:从上面可能不清楚:对于 TDataManager 对象的整个生命周期,只有一个线程写入数据,而不是几个可能竞争写访问的线程。所以这是 MREW 的一个特例。
【问题讨论】:
-
我对自己编写的无锁代码持谨慎态度,几乎不可能做到正确。至于 TMREWS:没有办法在典型机器上为您的用例计时,因为有不同的方法来实现它们,而 VCL 只为您提供一种。有关比较不同实现(包括时间)的文章,请参阅codeproject.com/KB/threads/testing_rwlocks.aspx
标签: multithreading delphi lock-free