前面几篇贴出的基于原子自旋锁的代码中,会存在一种极端应用场景中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.