锁定多个读者单独作家 [英] Lock free multiple readers single writer
问题描述
有两个选项可以解决这个问题:
- 使用TMultiReadExclusiveWriteSynchronizer
- 通过使用无锁方式来消除任何阻止
对于2.到目前为止,我已经得到以下内容(任何没有关系的代码都被忽略):
type
TDataManager = class
private
FAccessCount:integer;
FData:TDataClass;
public
procedure read(out _Some:integer; out _Data:double);
程序写(_Some:integer; _Data:double);
结束
程序TDataManager.Read(out _Some:integer; out _Data:double);
var
数据:TDAtaClass;
begin
InterlockedIncrement(FAccessCount);
try
//确保我们从同一个TDataClass实例获取两个值
数据:= FData;
//读取实际数据
_Some:= Data.Some;
_Data:= Data.Data;
finally
InterlockedDecrement(FAccessCount);
结束
结束
程序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));
//现在,FData指向新的实例,但是可能还有
//读者
ReaderCount:= InterlockedDecrement(FAccessCount);
如果ReaderCount = 0然后
//没有活跃的读者,所以我们可以安全地释放旧实例
FreeAndNil(OldData)
else begin
///这里是问题
end;
end;
不幸的是,在替换了OldData实例后,有一个很小的问题,如果Read方法(ReaderCount = 0)当前没有其他线程,它可以安全地但是如果不是这样,我该怎么办?
我可以直接在下一次调用中存储它,然后将其处理,但Windows调度理论上可以让读者线程在内部休眠Read方法仍然有一个对OldData的引用。
如果您看到上述代码有其他问题,请告诉我。这将在具有多个内核的计算机上运行,并且上述方法将被非常频繁地调用。
如果重要:我正在使用Delphi 2007与内置内存经理。我知道内存管理器在创建一个新类时可能会强制执行一些锁定,但是我想暂时忽略它。
编辑:可能还不清楚从上述:对于TDataManager对象的整个生命周期,只有一个线程写入数据,而不是几个可能竞争写访问的线程。所以这是MREW的一个特例。
我不知道任何无锁(或微锁定在上面的例子中)可以在Intel86代码上实现的MREW方法。
对于小(快速到期)锁,从 OmniThreadLibrary 工作正常:
type
TOmniMREW = record
strict private
omrewReference:integer; //Reference.Bit0是正在写入标志
public
procedure EnterReadLock;一致;
程序EnterWriteLock;一致;
程序ExitReadLock;一致;
程序ExitWriteLock;一致;
结束{tomniMREW}
procedure TOmniMREW.EnterReadLock;
var
currentReference:integer;
begin
//等待写程序重置写入标志,所以Reference.Bit0必须为0而不是增加引用
repeat
currentReference:= omrewReference AND NOT 1;
until currentReference = InterlockedCompareExchange(omrewReference,currentReference + 2,currentReference);
结束{TomniMREW.EnterReadLock}
procedure TOmniMREW.EnterWriteLock;
var
currentReference:integer;
begin
//等待写程序重置写标志,所以omrewReference.Bit0必须为0,然后设置omrewReference.Bit0
repeat
currentReference:= omrewReference AND NOT 1;
until currentReference = InterlockedCompareExchange(omrewReference,currentReference + 1,currentReference);
//现在等待所有读者
重复
直到omrewReference = 1;
结束{TomniMREW.EnterWriteLock}
procedure TomniMREW.ExitReadLock;
begin
//减少omrewReference
InterlockedExchangeAdd(omrewReference,-2);
结束{TomniMREW.ExitReadLock}
procedure TomniMREW.ExitWriteLock;
begin
omrewReference:= 0;
结束{tomniMREW.ExitWriteLock}
我刚刚注意到一个可能的对齐问题 - 代码应该检查omrewReference是4对齐。将通知作者。
I have got an in memory data structure that is read by multiple threads and written by only one thread. Currently I am using a critical section to make this access threadsafe. Unfortunately this has the effect of blocking readers even though only another reader is accessing it.
There are two options to remedy this:
- use TMultiReadExclusiveWriteSynchronizer
- do away with any blocking by using a lock free approach
For 2. I have got the following so far (any code that doesn't matter has been left out):
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;
Unfortunately there is the small problem of getting rid of the OldData instance after it has been replaced. If no other thread is currently within the Read method (ReaderCount=0), it can safely be disposed and that's it. But what can I do if that's not the case? I could just store it until the next call and dispose it there, but Windows scheduling could in theory let a reader thread sleep while it is within the Read method and still has got a reference to OldData.
If you see any other problem with the above code, please tell me about it. This is to be run on computers with multiple cores and the above methods are to be called very frequently.
In case this matters: I am using Delphi 2007 with the builtin memory manager. I am aware that the memory manager probably enforces some lock anyway when creating a new class but I want to ignore that for the moment.
Edit: It may not have been clear from the above: For the full lifetime of the TDataManager object there is only one thread that writes to the data, not several that might compete for write access. So this is a special case of MREW.
I don't know of any lock-free (or micro-locking as in your example above) MREW approach that could be implemented on Intel86 code.
For small (fast-expiring) locks a spinning approach from the OmniThreadLibrary works fine:
type
TOmniMREW = record
strict private
omrewReference: integer; //Reference.Bit0 is 'writing in progress' flag
public
procedure EnterReadLock; inline;
procedure EnterWriteLock; inline;
procedure ExitReadLock; inline;
procedure ExitWriteLock; inline;
end; { TOmniMREW }
procedure TOmniMREW.EnterReadLock;
var
currentReference: integer;
begin
//Wait on writer to reset write flag so Reference.Bit0 must be 0 than increase Reference
repeat
currentReference := omrewReference AND NOT 1;
until currentReference = InterlockedCompareExchange(omrewReference, currentReference + 2, currentReference);
end; { TOmniMREW.EnterReadLock }
procedure TOmniMREW.EnterWriteLock;
var
currentReference: integer;
begin
//Wait on writer to reset write flag so omrewReference.Bit0 must be 0 then set omrewReference.Bit0
repeat
currentReference := omrewReference AND NOT 1;
until currentReference = InterlockedCompareExchange(omrewReference, currentReference + 1, currentReference);
//Now wait on all readers
repeat
until omrewReference = 1;
end; { TOmniMREW.EnterWriteLock }
procedure TOmniMREW.ExitReadLock;
begin
//Decrease omrewReference
InterlockedExchangeAdd(omrewReference, -2);
end; { TOmniMREW.ExitReadLock }
procedure TOmniMREW.ExitWriteLock;
begin
omrewReference := 0;
end; { TOmniMREW.ExitWriteLock }
I just noticed a possible alignment issue here - the code should check that omrewReference is 4-aligned. Will notify the author.
这篇关于锁定多个读者单独作家的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!