mormot.core.os--TSynLocker和TSynLocked
TLightLock
- { **************** TSynLocker/TSynLocked 和 低级线程特性 }
- type
- /// 一个轻量级的独占非重入锁,存储在 PtrUInt 值中
- // - 在自旋一段时间后调用 SwitchToThread,但不使用任何读写操作系统API
- // - 警告:方法是非重入的,即在一个裸调用中两次调用 Lock 会导致死锁:
- // 对于需要重入方法的情况,请使用 TRWLock 或 TSynLocker/TOSLock
- // - 多个轻量级锁,每个保护少量变量(如列表),可能比更全局的 TOSLock/TRWLock 更高效
- // - 我们的轻量级锁预计保持时间非常短(几个CPU周期):
- // 如果锁可能阻塞太长时间,请使用 TSynLocker 或 TOSLock
- // - TryLock/UnLock 可用于线程安全地获取共享资源
- // - 在 CPU32 上占用 4 字节,在 CPU64 上占用 8 字节
- {$ifdef USERECORDWITHMETHODS}
- TLightLock = record
- {$else}
- TLightLock = object
- {$endif USERECORDWITHMETHODS}
- private
- Flags: PtrUInt; // 标志位
- // 由 Lock 方法在内联时调用的低级函数
- procedure LockSpin;
- public
- /// 如果实例未被初始化为 0,则调用此方法
- // - 例如,如果 TLightLock 被定义为类字段,则不需要此方法
- procedure Init;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 可用于将实例作为 TOSLock 进行终结处理
- // - 不执行任何操作 - 仅与 TOSLock 兼容
- procedure Done;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 进入独占非重入锁
- procedure Lock;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 尝试进入独占非重入锁
- // - 如果返回 true,则调用者最终应调用 UnLock()
- // - 也可用于线程安全地获取共享资源
- function TryLock: boolean;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 检查独占非重入锁是否已被获取
- function IsLocked: boolean;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 离开独占非重入锁
- procedure UnLock;
- {$ifdef HASINLINE} inline; {$endif}
- end;
复制代码 TRWLightLock
- /// 一个轻量级的支持多个读操作/独占写操作的非升级锁
- // - 在自旋一段时间后调用 SwitchToThread,但不使用任何读写操作系统API
- // - 警告:读锁是可重入的并允许并发访问,但在读锁内部或另一个写锁内部调用 WriteLock 会导致死锁
- // - 如果您需要一个可升级的锁,请考虑使用 TRWLock - 但对于大多数读操作,
- // TRWLightLock.ReadLock/ReadUnLock/WriteLock 模式比升级更快
- // - 我们的轻量级锁预计保持时间非常短(几个CPU周期):
- // 如果锁可能阻塞太长时间,请使用 TSynLocker 或 TOSLock
- // - 多个轻量级锁,每个保护少量变量(如列表),可能比更全局的 TOSLock/TRWLock 更高效
- // - 在 CPU32 上占用 4 字节,在 CPU64 上占用 8 字节
- {$ifdef USERECORDWITHMETHODS}
- TRWLightLock = record
- {$else}
- TRWLightLock = object
- {$endif USERECORDWITHMETHODS}
- private
- Flags: PtrUInt; // 标志位,位 0 = 写锁,>0 = 读锁计数器
- // 由 Lock 方法在内联时调用的低级函数
- procedure ReadLockSpin;
- procedure WriteLockSpin;
- public
- /// 如果实例未被初始化为 0,则调用此方法
- // - 例如,如果 TRWLightLock 被定义为类字段,则不需要此方法
- procedure Init;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 进入不可升级的多读锁
- // - 读锁维护一个线程安全的计数器,因此是可重入和非阻塞的
- // - 警告:在读锁之后嵌套调用 WriteLock 会导致死锁
- procedure ReadLock;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 尝试进入不可升级的多读锁
- // - 如果返回 true,则调用者最终应调用 ReadUnLock
- // - 读锁维护一个线程安全的计数器,因此是可重入和非阻塞的
- // - 警告:在读锁之后嵌套调用 WriteLock 会导致死锁
- function TryReadLock: boolean;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 离开不可升级的多读锁
- procedure ReadUnLock;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 进入不可重入且不可升级的独占写锁
- // - 警告:在读锁或另一个写锁之后嵌套调用 WriteLock 会导致死锁
- procedure WriteLock;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 尝试进入不可重入且不可升级的独占写锁
- // - 如果返回 true,则调用者最终应调用 WriteUnLock
- // - 警告:在读锁或另一个写锁之后嵌套调用 TryWriteLock 会导致死锁
- function TryWriteLock: boolean;
- {$ifdef HASINLINE} inline; {$endif}
-
- /// 离开不可重入且不可升级的独占写锁
- procedure WriteUnLock;
- {$ifdef HASINLINE} inline; {$endif}
- end;
复制代码 TLockedList
- /// 指向TLockedList中一个数据条目的指针
- PLockedListOne = ^TLockedListOne;
- /// TLockedList中一个数据条目的抽象父类,存储两个PLockedListOne指针
- // - TLockedList应该存储以这些字段开头的非托管记录
- // - sequence字段包含一个递增的、基于随机种子的30位整数(大于65535),
- // 以避免实例回收时出现的ABA问题
- TLockedListOne = record
- next, prev: pointer; // 指向下一个和上一个条目的指针
- sequence: PtrUInt; // 序列号,用于解决ABA问题
- end;
- /// 用于终结一个TLockedListOne实例的可选回调事件
- TOnLockedListOne = procedure(one: PLockedListOne) of object;
- /// 线程安全的双链表,包含TLockedListOne后代的回收机制
- {$ifdef USERECORDWITHMETHODS}
- TLockedList = record
- {$else}
- TLockedList = object
- {$endif USERECORDWITHMETHODS}
- private
- fHead, fBin: pointer; // 分别指向链表头部和回收箱的指针
- fSize: integer; // 列表中每个实例的大小(包括TLockedListOne头部)
- fSequence: PtrUInt; // 全局序列号生成器
- fOnFree: TOnLockedListOne; // 当实例被释放时调用的回调
- public
- /// 线程安全的访问锁
- Safe: TLightLock;
-
- /// 当前列表中存储的TLockedListOne实例数量(不包括回收箱中的实例)
- Count: integer;
-
- /// 初始化继承自TLockedListOne的大小的存储
- procedure Init(onesize: PtrUInt; const onefree: TOnLockedListOne = nil);
-
- /// 释放所有存储的内存
- procedure Done;
-
- /// 在线程安全的O(1)过程中分配一个新的PLockedListOne数据实例
- function New: pointer;
-
- /// 在线程安全的O(1)过程中释放一个已使用的PLockedListOne数据实例
- function Free(one: pointer): boolean;
-
- /// 释放当前存储在此列表中的所有TLockedListOne实例
- // - 不会将任何实例移动到内部回收箱
- procedure Clear;
-
- /// 释放内部回收箱中所有待回收的项
- // - 返回从内部收集器中释放了多少项
- function EmptyBin: integer;
-
- /// 作为PLockedListOne双链表的原始访问存储的项
- property Head: pointer
- read fHead;
-
- /// 存储的每个实例的大小,包括其TLockedListOne头部
- property Size: integer
- read fSize;
- end;
复制代码 TSynLocker
这段代码定义了一个 TSynLocker类型,它允许跨平台地为任何类实例添加锁定方法,以确保线程安全。它提供了多种锁定机制(如共享锁、读写锁和无锁),以及安全访问内部存储的值的方法。这些特性使得 TSynLocker成为处理多线程编程中同步题目的一个强盛工具。
TAutoLock
- /// 指向TSynLocker互斥锁实例的指针
- // - 另请参见NewSynLocker和TSynLocker.DoneAndFreemem函数
- PSynLocker = ^TSynLocker;
- /// TAutoLocker.ProtectMethod和TSynLocker.ProtectMethod使用的原始类
- // - 在此定义以供mormot.core.data.pas中的TAutoLocker使用
- TAutoLock = class(TInterfacedObject)
- protected
- fLock: PSynLocker; // 指向TSynLocker的指针
- public
- constructor Create(aLock: PSynLocker); // 构造函数,接受一个PSynLocker参数
- destructor Destroy; override; // 析构函数,重写自TInterfacedObject
- end;
复制代码 TSynEvent
- /// 我们的轻量级跨平台TEvent类似组件
- // - 在Windows上,直接调用CreateEvent/ResetEvent/SetEvent API
- // - 在Linux上,将使用阻塞和非信号量模式的eventfd()
- // - 在其他POSIX系统上,将使用比TEvent BasicEvent更轻的PRTLEvent
- // - 唯一限制是我们不知道WaitFor是被信号触发还是超时,
- // 但实际上这并不是一个大问题,因为大多数代码不需要这个信息
- // 或者已经在其实现逻辑中有了自己的标志
- TSynEvent = class
- protected
- fHandle: pointer; // Windows THandle或FPC PRTLEvent
- fFD: integer; // 用于eventfd()
- public
- /// 初始化跨平台事件实例
- constructor Create;
- /// 终结跨平台事件实例
- destructor Destroy; override;
- /// 忽略任何挂起的事件,以便WaitFor将在下次SetEvent时被设置
- procedure ResetEvent;
- {$ifdef OSPOSIX} inline; {$endif}
- /// 触发任何挂起的事件,释放WaitFor/WaitForEver方法
- procedure SetEvent;
- {$ifdef OSPOSIX} inline; {$endif}
- /// 等待,直到另一个线程调用SetEvent,具有最大时间
- // - 如果被信号触发或超时,则不返回
- // - 警告:您应该一次只从一个线程等待
- procedure WaitFor(TimeoutMS: integer);
- {$ifdef OSPOSIX} inline; {$endif}
- /// 无限期等待,直到另一个线程调用SetEvent
- procedure WaitForEver;
- {$ifdef OSPOSIX} inline; {$endif}
- /// 在检查终止标志和此事件的同时,分步骤调用SleepHiRes()
- function SleepStep(var start: Int64; terminated: PBoolean): Int64;
- /// 如果使用了eventfd() API,则可用于调整算法
- function IsEventFD: boolean;
- {$ifdef HASINLINE} inline; {$endif}
- end;
复制代码 NewSynLocker
- /// 从堆初始化一个TSynLocker实例
- // - 调用DoneandFreeMem来释放相关内存和操作系统互斥锁
- // - 例如,在TSynPersistentLock中使用以减少类实例大小
- function NewSynLocker: PSynLocker;
复制代码 TSynLocked
- type
- {$M+} // 开启内存管理消息
- /// TSynPersistentLock的一个持久性无关替代方案
- // - 当不需要自定义JSON持久性时,可以用作基类
- // - 可以考虑将TRWLock字段用作更轻量级的多读/独占写选项
- TSynLocked = class
- protected
- fSafe: PSynLocker; // TSynLocker会增加继承字段的偏移量
- public
- /// 初始化实例及其关联的锁
- // - 定义为virtual,就像TObjectWithCustomCreate/TSynPersistent一样
- constructor Create; virtual;
- /// 终结实例及其关联的锁
- destructor Destroy; override;
- /// 访问关联的实例临界区
- // - 调用Safe.Lock/UnLock来保护此存储的多线程访问
- property Safe: PSynLocker
- read fSafe;
- end;
- {$M-} // 关闭内存管理消息
- /// TSynLocked层次的元类定义
- TSynLockedClass = class of TSynLocked;
复制代码 TLecuyerThreadSafe
- /// 线程安全的Pierre L'Ecuyer软件随机数生成器
- // - 仅用TLightLock包装TLecuyer
- // - 除非可能比threadvar稍快,否则不应使用
- {$ifdef USERECORDWITHMETHODS}
- TLecuyerThreadSafe = record
- {$else}
- TLecuyerThreadSafe = object
- {$endif USERECORDWITHMETHODS}
- public
- Safe: TLightLock;
- Generator: TLecuyer;
- /// 计算下一个生成的32位值
- function Next: cardinal; overload;
- /// 计算一个64位浮点数
- function NextDouble: double;
- /// 用随机字节异或某个内存缓冲区
- procedure Fill(dest: pointer; count: integer);
- /// 用7位ASCII随机文本填充某个string[31]
- procedure FillShort31(var dest: TShort31);
- end;
- TThreadIDDynArray = array of TThreadID; // 线程ID动态数组类型
- var
- /// 全局线程安全的Pierre L'Ecuyer软件随机数生成器
- // - 除非可能比threadvar稍快,否则不应使用
- SharedRandom: TLecuyerThreadSafe;
复制代码 与线程、CPU核心和事件相干的类型和函数
[code]{$ifdef OSPOSIX} /// 可设置为TRUE,以欺压SleepHiRes(0)调用POSIX sched_yield // - 在实践中,据报道在POSIX体系上存在题目 // - 即使是Linus Torvalds本人也对它的利用表示愤怒 - 例如,请参见 // https://www.realworldtech.com/forum/?threadid=189711&curpostid=189752 // - 您可以自己尝试它 SleepHiRes0Yield: boolean = false;{$endif OSPOSIX}/// 类似于Windows的sleep() API调用,真正实现跨平台// - 利用毫秒级分辨率// - SleepHiRes(0)在Windows上调用ThreadSwitch,但在POSIX版本中将等待10微秒// 除非欺压SleepHiRes0Yield为true(坏主意)// - 相对于RTL的Sleep()函数,如果在任何OS信号中断时返回ESysEINTR// - 警告:通常在Windows上等待下一个体系计时器中断,默认为每16毫秒一次;// 因此,永远不要依赖提供的毫秒值来猜测经过的时间,而应调用GetTickCount64procedure SleepHiRes(ms: cardinal); overload;/// 类似于Windows的sleep() API调用,但真正实现跨平台// 并在等待期间检查Terminated标志以快速响应中断// - 如果terminated^被设置为true(terminatedvalue),则返回truefunction SleepHiRes(ms: cardinal; var terminated: boolean; terminatedvalue: boolean = true): boolean; overload;/// 调用SleepHiRes(),思量活动的步长,在0/1/5/50/120-250毫秒步长中// - 范围设计激进,以响应性为代价燃烧一些CPU// - 当发生某些活动时,应重置start := 0,或在Windows上设置start := -1// 以避免任何SleepHiRes(0) = SwitchToThread调用// - 可选地在terminated^被设置或事件被信号触发时返回// - 返回当前的GetTickCount64值function SleepStep(var start: Int64; terminated: PBoolean = nil): Int64;/// 计算最佳就寝时间作为0/1/5/50然后120-250毫秒步长// - 范围设计激进,以响应性为代价燃烧一些CPUfunction SleepDelay(elapsed: PtrInt): PtrInt;/// 计算最佳就寝时间,类似于SleepStep,在0/1/5/50/120-250毫秒步长中// - 范围设计激进,以响应性为代价燃烧一些CPU// - start=0将用tix填充其值,start |