DelphiXE10.2.3实现线程安全访问数据和对象(六)——修复原子自旋锁在极端应用场景下的ABA问题

     前面几篇贴出的基于原子自旋锁的代码中,会存在一种极端应用场景中CAS 的ABA问题:

     AB两个线程同时执行到一条CAS(类似CompareExchange函数)前时,B线程将链表头保存本地变量中,而A线程接着取走链表头上的这个数据,然后在B线程执行到CAS时,A线程又将取走的数据返回给链表头,B线程的链表关系被打乱。

     这种情况在我的业务应用中不会发生是因为我取走数据后还要进行业务处理,再返还链表时,已经经过10多毫秒了,早超过线程并发的时间片,但为了彻底杜绝ABA问题,在DIOCP1号群讨论时得到王烨老师的指点,在此感谢王烨老师的帮助,发布一个HASH表的修正版本,其余版本可参考此代码进行修改,方法比较烧脑,敬请注意安全大笑


{ ********************************************************************************
  组件名称:无锁链表
  组件说明:
  1、多线程安全读写,没有使用线程互斥锁类低效率方法
  2、使用一个单独的空闲链表来实现一个简单的内存池,避免大量的Getmem和Freemem操作
  3、同时,由于使用了链表内存池,就把链表结构封装在内部,使调用者无法操作链表结构,增加可靠性
  4、重要的是,线程读写均衡的情况下,读写效率也奇高
  5、更重要的,跨平台!
  创建者:晴空无彩虹 QQ群:733975324
  版本号:0.2
  创建日期:2018-05-10
  修改日期:2018-05-31
  修改原因:原无锁链表可能存在极端特定情况下的原子ABA问题,
  该问题是由于之前只判断出入队节点的指针地址,可能存在A线程在CAS时挂起,B线程对相同地址节点出队后
  立即再入队,则A线程就会存入错误的节点,所以需要用特殊戳来转换每个节点地址,保证存入的节点数据
  是全局唯一的,方法很烧脑,但确实能彻底解决ABA问题
  注意事项:
  ******************************************************************************** }

unit uLockFreeLinkedList;

interface

uses System.Classes, System.SyncObjs, System.SysUtils;

type

  // 创建新的单链表和元素事件
  TCreateSingleLinkedList<T> = procedure(var Value: T) of object;
  // 释放单链表和元素事件
  TDestroySingleLinkedList<T> = procedure(var Value: T) of object;

  // 无锁链表
  TLockFreeLinkedList<T> = Class
  private type
    // 单链表
    PSingleLinkedList = ^TSingleLinkedList;

    TSingleLinkedList = record
      Next: PSingleLinkedList;
      Element: T;
    end;
  private
    Tag: NativeUInt;
    FLinkedListHead: PSingleLinkedList; // 链表头
    FIdleLinkedListHead: PSingleLinkedList; // 空闲链表
    FValidCount: integer; // 有效链表个数
    FIdleCount: integer; // 空闲链表个数
    FDoDestroy: boolean; // 准备释放对象了,赶快退出通道
    FCreateSingleLinkedList: TCreateSingleLinkedList<T>;
    FDestroySingleLinkedList: TDestroySingleLinkedList<T>;
    function GetValidCount: integer;
    function GetIdleCount: integer;
    // 内部压入操作
    function InternalPush(var Value: T): boolean;
    // 内部弹出操作
    function InternalPop(var Value: T): boolean;
    // 压入空闲链表
    procedure PushIdleLinkedList(var SingleLinkedList: PSingleLinkedList);
    // 弹出空闲链表
    function PopIdleLinkedList: PSingleLinkedList;
    function RawDataToAddress(const RawData: UInt64): Pointer;
    function AddressAndTagToRawData(const Address: Pointer;
      const Tag: NativeUInt): UInt64;
  public
    constructor Create();
    destructor Destroy; override;
    function Push(var Value: T): boolean;
    function Pop(var Value: T): boolean;
    // 有效链表个数
    property ValidCount: integer read GetValidCount;
    // 空闲链表个数
    property IdleCount: integer read GetIdleCount;
    // 创建新的单链表和元素事件
    property DoCreateSingleLinkedList: TCreateSingleLinkedList<T>
      read FCreateSingleLinkedList write FCreateSingleLinkedList;
    // 释放单链表和元素事件
    property DoDestroySingleLinkedList: TDestroySingleLinkedList<T>
      read FDestroySingleLinkedList write FDestroySingleLinkedList;
  End;

const
  __ShiftFactor = {$IFDEF CPU64BITS}48{$ELSE}32{$ENDIF};
  // 32:$0000000080000000; 64:$0000800000000000;
  __SignBit = UInt64(1) shl (__ShiftFactor - 1);
  // 32:$00000000FFFFFFFF; 64:$0000FFFFFFFFFFFF;
  __AddressMask = (UInt64(1) shl __ShiftFactor) - 1;

implementation

constructor TLockFreeLinkedList<T>.Create();
begin
  inherited;
  Tag := 0;
  FValidCount := 0;
  FIdleCount := 0;
  FDoDestroy := false;
  // 两个根链表初始化
  GetMem(FLinkedListHead, SizeOf(UInt64));
  PUInt64(FLinkedListHead)^ := 0;
  GetMem(FIdleLinkedListHead, SizeOf(UInt64));
  PUInt64(FIdleLinkedListHead)^ := 0;
end;

destructor TLockFreeLinkedList<T>.Destroy;
var
  Value: T;
  SingleLinkedList: PSingleLinkedList;
begin
  FDoDestroy := true;
  sleep(10);
  // 先释放所有元素
  while FLinkedListHead^.Next <> nil do
  begin
    self.InternalPop(Value);
    // 将单链表中的元素都交给调用者去释放
    if Assigned(DoDestroySingleLinkedList) then
      DoDestroySingleLinkedList(Value);
  end;
  // 再释放所有链表
  while FIdleLinkedListHead^.Next <> nil do
  begin
    SingleLinkedList := PopIdleLinkedList();
    freemem(SingleLinkedList,SizeOf(UInt64));
  end;
  freemem(FLinkedListHead, SizeOf(UInt64));
  freemem(FIdleLinkedListHead, SizeOf(UInt64));
  inherited;
end;

function TLockFreeLinkedList<T>.RawDataToAddress(const RawData: UInt64)
  : Pointer; // inline;
{$IFDEF CPU64BITS}
begin
  Result := Pointer((RawData and __AddressMask) or
    not((RawData and __SignBit) - 1));
end;
{$ENDIF CPU64BITS}
{$IFDEF CPU32BITS}
begin
  Result := Pointer(RawData);
end;
{$ENDIF CPU32BITS}

function TLockFreeLinkedList<T>.AddressAndTagToRawData(const Address: Pointer;
  const Tag: NativeUInt): UInt64; // inline;
{$IFDEF CPU64BITS}
begin
  Result := UInt64(UIntPtr(Address) and __AddressMask) or
    (UInt64(Tag) shl __ShiftFactor);
end;
{$ENDIF CPU64BITS}
{$IFDEF CPU32BITS}
{$IF __ShiftFactor = 32}

type
  __m64 = record
    Lo, Hi: UInt32;
  end;

begin
  __m64(Result).Hi := Tag;
  __m64(Result).Lo := UIntPtr(Address);
end;
{$ELSE}
begin
  Result := UInt64(UIntPtr(Address) or (UInt64(Tag) shl __ShiftFactor));
end;
{$ENDIF}
{$ENDIF CPU32BITS}

function TLockFreeLinkedList<T>.GetValidCount: integer;
begin
  Result := AtomicCmpExchange(FValidCount, 0, 0);
end;

function TLockFreeLinkedList<T>.GetIdleCount: integer;
begin
  Result := AtomicCmpExchange(FIdleCount, 0, 0);
end;

// 压入空闲链表
procedure TLockFreeLinkedList<T>.PushIdleLinkedList(var SingleLinkedList
  : PSingleLinkedList);
var
  RawData, NewRawData: UInt64;
  TaggedStackPtr: PUInt64;
begin
  TaggedStackPtr := PUInt64(FIdleLinkedListHead);
  NewRawData := AddressAndTagToRawData(SingleLinkedList, AtomicIncrement(Tag));
  repeat
    RawData := TaggedStackPtr^;
    SingleLinkedList^.Next := PSingleLinkedList(RawDataToAddress(RawData));
  until AtomicCmpExchange(TaggedStackPtr^, NewRawData, RawData) = RawData;
  TInterlocked.Increment(FIdleCount);
end;

// 弹出空闲链表
function TLockFreeLinkedList<T>.PopIdleLinkedList: PSingleLinkedList;
var
  NewTag: NativeUInt;
  RawData, NewRawData: UInt64;
  TaggedStackPointer: PUInt64;
begin
  TaggedStackPointer := PUInt64(FIdleLinkedListHead);
  NewTag := AtomicIncrement(Tag);
  repeat
    RawData := TaggedStackPointer^;
    Result := PSingleLinkedList(RawDataToAddress(RawData));
    if (Result = nil) and (not FDoDestroy) then
    begin
      Getmem(result, SizeOf(UInt64));
      PUInt64(result)^ := 0;
      exit;
    end;
    NewRawData := AddressAndTagToRawData(Result^.Next, NewTag)
  until AtomicCmpExchange(TaggedStackPointer^, NewRawData, RawData) = RawData;
  TInterlocked.Decrement(FIdleCount);
end;

function TLockFreeLinkedList<T>.Push(var Value: T): boolean;
begin
  Result := false;
  // 如果已经在释放对象,就直接退出
  if FDoDestroy then
    Exit;
  // 内部压入操作
  Result := InternalPush(Value);
end;

function TLockFreeLinkedList<T>.Pop(var Value: T): boolean;
begin
  Result := false;
  // 如果已经在释放对象,就直接退出
  if FDoDestroy then
    Exit;
  // 内部弹出操作
  Result := InternalPop(Value);
end;

// 内部压入操作
function TLockFreeLinkedList<T>.InternalPush(var Value: T): boolean;
var
  RawData, NewRawData: UInt64;
  TaggedStackPtr: PUInt64;
  SingleLinkedList: PSingleLinkedList;
begin
  Result := false;
  // 从空闲链表中取一个出来使用
  SingleLinkedList := PopIdleLinkedList();
  if SingleLinkedList = nil then
    Exit;
  SingleLinkedList.Element := Value;
  TaggedStackPtr := PUInt64(FLinkedListHead);
  NewRawData := AddressAndTagToRawData(SingleLinkedList, AtomicIncrement(Tag));
  repeat
    RawData := TaggedStackPtr^;
    SingleLinkedList^.Next := PSingleLinkedList(RawDataToAddress(RawData));
  until AtomicCmpExchange(TaggedStackPtr^, NewRawData, RawData) = RawData;
  TInterlocked.Increment(FValidCount);
  Result := true;
end;

// 内部弹出操作
function TLockFreeLinkedList<T>.InternalPop(var Value: T): boolean;
var
  NewTag: NativeUInt;
  RawData, NewRawData: UInt64;
  TaggedStackPointer: PUInt64;
  SingleLinkedList: PSingleLinkedList;
begin
  Result := false;
  TaggedStackPointer := PUInt64(FLinkedListHead);
  NewTag := AtomicIncrement(Tag);
  repeat
    RawData := TaggedStackPointer^;
    SingleLinkedList := PSingleLinkedList(RawDataToAddress(RawData));
    if SingleLinkedList = nil then
      if Assigned(DoCreateSingleLinkedList) and not FDoDestroy then
      begin
        DoCreateSingleLinkedList(Value);
        Result := true;
        Exit;
      end
      else // 如果调用者没有创建单链表事件,就直接返回
        Exit;
    NewRawData := AddressAndTagToRawData(SingleLinkedList^.Next, NewTag)
  until AtomicCmpExchange(TaggedStackPointer^, NewRawData, RawData) = RawData;
  Value := SingleLinkedList^.Element;
  // 压入空闲链表
  PushIdleLinkedList(SingleLinkedList);
  TInterlocked.Decrement(FValidCount);
  Result := true;
end;

end.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值