锁定多个读者单独作家 [英] Lock free multiple readers single writer

查看:162
本文介绍了锁定多个读者单独作家的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个内存数据结构,由多个线程读取,只有一个线程写入。目前我正在使用一个关键部分来使这个访问线程安全。不幸的是,即使只有另一位读者正在访问它,这也是阻止读者的效果。



有两个选项可以解决这个问题:


  1. 使用TMultiReadExclusiveWriteSynchronizer

  2. 通过使用无锁方式来消除任何阻止

对于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:

  1. use TMultiReadExclusiveWriteSynchronizer
  2. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆