首页 > 其他分享 >mormot.core.threads--TSynQueue

mormot.core.threads--TSynQueue

时间:2024-07-08 16:53:41浏览次数:9  
标签:core TSynQueue 示例 -- 存储 mormot 队列 任务 线程

mormot.core.threads--TSynQueue

以下是对 mormot.core.threads中部分代码的翻译,特别是关于 TSynQueue类的部分:

const
  // 在这里定义以避免在uses子句中显式链接到syncobjs单元
  wrSignaled = syncobjs.wrSignaled; // 等待结果:已发出信号
  wrTimeout  = syncobjs.wrTimeout;  // 等待结果:超时
  wrError    = syncobjs.wrError;    // 等待结果:错误

type
  // 在这里定义以避免在uses子句中显式链接到syncobjs单元
  // - 请注意,您可能更想使用来自mormot.core.os.pas的TSynEvent
  TWaitResult = syncobjs.TWaitResult; // 等待操作的结果类型

  // 在这里定义以避免在uses子句中显式链接到syncobjs单元
  // - 请注意,您可能更想使用来自mormot.core.os.pas的TSynEvent
  TEvent = syncobjs.TEvent; // 事件对象类型

{$endif PUREMORMOT2}

type
  // TThread的动态数组类型
  TThreadDynArray = array of TThread;

  // 由本单元引发的异常类
  ESynThread = class(ESynException);

{ ************ 线程安全的TSynQueue和TPendingTaskList }

type
  // 线程安全的FIFO(先进先出)记录队列
  // - 内部使用TDynArray存储,采用滑动算法,比FPC或Delphi的TQueue或简单的TDynArray.Add/Delete更高效
  // - 如果需要,支持TSynPersistentStore二进制持久化
  // - 此结构也是线程安全的
  TSynQueue = class(TSynPersistentStore)
  protected
    // ...(省略了保护成员的详细翻译,它们主要是内部实现细节)
  public
    /// 初始化队列存储
    // - aTypeInfo应该是存储在此TSynQueue实例中的值的动态数组TypeInfo() RTTI指针
    // - 可以选择性地为此实例分配一个名称
    constructor Create(aTypeInfo: PRttiInfo; const aName: RawUtf8 = ''); reintroduce; virtual;
    /// 释放存储
    // - 将释放所有内部存储的值,并调用WaitPopFinalize
    destructor Destroy; override;
    /// 将一个项目推入队列
    // - 此方法是线程安全的,因为它会锁定实例
    procedure Push(const aValue);
    /// 从队列中提取一个项目,作为FIFO(先进先出)
    // - 如果aValue已被填充为挂起的项目,则返回true,并且该项目从队列中移除(如果不想移除,请使用Peek)
    // - 如果队列为空,则返回false
    // - 此方法是线程安全的,因为它会锁定实例
    function Pop(out aValue): boolean;
    /// 从队列中提取一个匹配的项目,作为FIFO(先进先出)
    // - 将当前挂起的项目与aAnother值进行比较
    function PopEquals(aAnother: pointer; aCompare: TDynArraySortCompare;
      out aValue): boolean;
    /// 从队列中查找一个项目,作为FIFO(先进先出)
    // - 如果aValue已被填充为挂起的项目,则返回true,并且该项目不会从队列中移除(与Pop方法不同)
    // - 如果队列为空,则返回false
    // - 此方法是线程安全的,因为它会锁定实例
    function Peek(out aValue): boolean;
    /// 等待并从队列中提取一个项目,作为FIFO(先进先出)
    // - 如果在指定的aTimeoutMS时间内aValue已被填充为挂起的项目,则返回true
    // - 如果没有在时间内将项目推入队列,或者已调用WaitPopFinalize,则返回false
    // - aWhenIdle可用于空闲时处理消息,例如VCL/LCL的Application.ProcessMessages
    // - 您可以选择在返回之前比较挂起的项目(当多个线程将项目放入队列时可能很有用)
    // - 此方法是线程安全的,但仅在需要时锁定实例
    function WaitPop(aTimeoutMS: integer; const aWhenIdle: TThreadMethod;
      out aValue; aCompared: pointer = nil;
      aCompare: TDynArraySortCompare = nil): boolean;
    /// 在队列中等待查找一个项目,作为FIFO(先进先出)
    // - 在aTimeoutMS时间内返回一个指向挂起项目的指针
    // - 保持Safe.ReadWriteLock,因此调用者可以检查其内容,然后如果它是预期的,则调用Pop(),并最终调用Safe.ReadWriteUnlock
    // - 如果没有在时间内将项目推入队列,则返回nil
    // - 此方法是线程安全的,但仅在需要时锁定实例
    function WaitPeekLocked(aTimeoutMS: integer;
      const aWhenIdle: TThreadMethod): pointer;
    /// 确保任何挂起或未来的WaitPop()立即返回false
    // - 总是由Destroy析构函数调用
    // - 也可以从例如UI的OnClose事件中调用,以避免任何锁定
    // - 此方法是线程安全的,但仅在需要时锁定实例
    procedure WaitPopFinalize(aTimeoutMS: integer = 100);
    /// 删除此队列中当前存储的所有项目,并清空其容量
    // - 此方法是线程安全的,因为它会锁定实例
    procedure Clear;
    /// 用存储的队列项目初始化一个动态数组
    // - aDynArrayValues应该是Create方法中aTypeInfo定义的变量
    // - 您可以检索一个可选的TDynArray包装器,例如用于二进制或JSON持久化
    // - 此方法是线程安全的,并将复制队列数据
    procedure Save(out aDynArrayValues; aDynArray: PDynArray = nil); overload;
    /// 返回当前存储在此队列中的项目数
    // - 此方法不是线程安全的,因此返回的值应是指示性的,或者您应使用显式的Safe锁/解锁
    // - 如果您想检查队列是否为空,请调用Pending
    function Count: integer;
    /// 返回当前在内存中保留的槽位数
    // - 队列具有优化的自动调整大小算法,您可以使用此方法返回其当前容量
    // - 此方法不是线程安全的,因此返回的值是指示性的
    function Capacity: integer;
    /// 如果队列中有一些项目当前挂起,则返回true
    // - 比检查Count=0更快,并且比Pop或Peek快得多
    // - 此方法不是线程安全的,因此返回的值是指示性的
    function Pending: boolean;
      {$ifdef HASINLINE}inline;{$endif}
  end;

这个翻译提供了对 TSynQueue类及其成员、方法和属性的概述,以便更好地理解其设计目的和使用方式。请注意,翻译过程中省略了保护成员的详细翻译,因为它们主要是内部实现细节,对于外部使用来说不是必需的。

在Free Pascal环境下,使用 TSynQueue类的一个示例会涉及创建队列实例、向队列中添加元素、从队列中提取元素,以及处理可能的并发访问。由于 TSynQueue是线程安全的,因此它非常适合在多线程应用程序中使用。然而,为了简化示例,我们将在一个单线程环境中展示其基本用法。

请注意,由于 TSynQueue可能是特定于某个库(如mORMot)的,因此您可能需要确保该库已被正确安装并包含在您的项目中。以下是一个简化的使用示例:

program TSynQueueExample;

{$MODE DELPHI}
{$APPTYPE CONSOLE}

uses
  SysUtils, // 包含WriteLn等标准输出函数
  mormot.core.threads; 

type
  // 定义一个简单的记录类型,用于存储在TSynQueue中
  TMyData = record
    ID: Integer;
    Value: String;
  end;

var
  Queue: TSynQueue;
  Data: TMyData;

begin
  try
    // 创建TSynQueue实例,传递TMyData类型的TypeInfo
    Queue := TSynQueue.Create(TypeInfo(TMyDataArray), 'MyDataQueue');
    try
      // 向队列中添加数据
      Queue.Push(TMyData.Create(1, 'First'));
      Queue.Push(TMyData.Create(2, 'Second'));
      Queue.Push(TMyData.Create(3, 'Third'));

      // 注意:上面的Push调用实际上是有问题的,因为TMyData是一个记录类型,
      // 它不是通过Create方法创建的。这里只是为了演示如何调用Push。
      // 在实际使用中,您应该直接传递记录的值,如下所示:
      // Queue.Push((ID: 1; Value: 'First')); // 但这取决于TSynQueue的实现是否支持记录值传递

      // 由于记录类型通常是通过值传递的,并且TSynQueue可能设计为存储记录的副本,
      // 因此您应该这样做:
      Queue.Push((ID: 1, Value: 'First'));
      Queue.Push((ID: 2, Value: 'Second'));
      Queue.Push((ID: 3, Value: 'Third'));

      // 从队列中提取数据(FIFO)
      while Queue.Pop(Data) do
      begin
        WriteLn('Popped Data: ID = ', Data.ID, ', Value = ', Data.Value);
      end;

      // 此时队列应为空
      if not Queue.Pending then
        WriteLn('Queue is empty.');

    finally
      // 销毁TSynQueue实例
      Queue.Free;
    end;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
  WriteLn('Program ended.');
end.

重要注意事项

  1. 在上面的示例中,我使用了 TMyDataArray作为 TypeInfo的参数,但实际上 TypeInfo(TMyDataArray)可能不是有效的,因为 TMyDataArray在示例中并未定义。通常,您应该传递记录类型本身的 TypeInfo,但 TSynQueue可能期望一个动态数组类型来存储其元素。然而,由于 TSynQueue的设计允许它存储记录的副本(而不是指针),因此您可能不需要定义一个动态数组类型。在实际使用中,您应该查阅 TSynQueue的文档以确定如何正确地传递 TypeInfo
  2. 记录类型通常是通过值传递的,并且上面的 Push调用示例假设 TSynQueue能够处理记录值的直接传递。这取决于 TSynQueue的具体实现。如果 TSynQueue被设计为存储指向记录的指针,那么您可能需要定义一个动态数组类型或使用其他机制来传递记录。
  3. 由于 TSynQueue是线程安全的,因此在多线程环境中使用时,您不需要担心并发访问问题。但是,在上面的示例中,我们为了简化而在一个单线程环境中展示了其基本用法。
  4. 请确保将 'YourSynapseUnit'替换为实际包含 TSynQueue定义的单元名称。如果 TSynQueue是mORMot库的一部分,那么您可能需要包含mORMot的相应单元。

以下是对 TPendingTaskList及其相关类型的翻译,包括其保护类型、构造函数、方法和属性:

type
  /// 内部项定义,用于TPendingTaskList存储
  // 该记录定义了待执行任务的时间戳和任务内容(以RawByteString形式存储)
  TPendingTaskListItem = packed record
    /// 当TPendingTaskList.GetTimestamp达到此值时,应执行该任务
    Timestamp: Int64;
    /// 与此时间戳相关联的任务,以原始二进制字符串形式存储
    Task: RawByteString;
  end;

  /// 内部列表定义,用于TPendingTaskList存储
  // TPendingTaskListItem的动态数组
  TPendingTaskListItemDynArray = array of TPendingTaskListItem;

  /// 线程安全的任务列表,任务以RawByteString形式存储,并带有时间戳
  // - 您可以向内部列表添加任务,在给定延迟后执行,使用类似发布/查看的算法
  // - 执行延迟可能不准确,但会根据每次调用NextPendingTask和GetTimestamp的分辨率进行最佳猜测
  TPendingTaskList = class
  protected
    // 内部存储结构和同步访问
    fTask: TPendingTaskListItemDynArray; // 存储待执行任务的数组
    fTasks: TDynArrayLocked; // 对fTask数组的封装,提供线程安全的访问
    // 获取当前存储的任务数量(线程安全)
    function GetCount: integer;
    // 获取当前时间戳(默认为GetTickCount64)
    function GetTimestamp: Int64; virtual;
  public
    // 初始化列表的内存和资源
    constructor Create; reintroduce;
    // 添加一个任务,指定从当前时间开始的延迟(毫秒)
    procedure AddTask(aMilliSecondsDelayFromNow: integer;
      const aTask: RawByteString); virtual;
    // 添加多个任务,指定任务之间的延迟(毫秒)
    // - 第一个提供的延迟将从当前时间开始计算,然后指定下一个提供的任务之间的等待时间
    // - 也就是说,aMilliSecondsDelays不是绝对延迟
    procedure AddTasks(const aMilliSecondsDelays: array of integer;
      const aTasks: array of RawByteString);
    // 检索下一个待执行的任务
    // - 如果没有在当前时间可用的计划任务,则返回''
    // - 根据指定的延迟返回下一个任务
    function NextPendingTask: RawByteString; virtual;
    // 清空所有待执行的任务
    procedure Clear; virtual;
    // 访问内部存储的TPendingTaskListItem.Timestamp值
    // - 对应当前时间
    // - 默认实现返回GetTickCount64,在Windows下典型分辨率为16毫秒
    property Timestamp: Int64 read GetTimestamp;
    // 当前定义了多少个待执行任务
    property Count: integer read GetCount;
    // 对内部任务列表的直接低级访问
    // - 警告:此动态数组的长度是列表的容量:请使用Count属性来检索存储的项的确切数量
    // - 使用Safe.Lock/TryLock与try ... finally Safe.Unlock块进行线程安全的访问
    // - 项按时间戳递增存储,即第一项是NextPendingTask方法将返回的下一个项
    property Task: TPendingTaskListItemDynArray read fTask;
  end;

TPendingTaskList类提供了一种机制来存储和按计划执行一系列任务,每个任务都与一个时间戳相关联。通过调用 AddTaskAddTasks方法,您可以将任务添加到列表中,这些任务将在指定的延迟后执行。NextPendingTask方法用于检索下一个待执行的任务,而 Clear方法用于清空整个任务列表。

注意,TPendingTaskList类中的 Timestamp属性和 GetTimestamp方法是用于确定何时执行任务的关键。GetTimestamp方法默认返回 GetTickCount64的值,但在子类中可以根据需要进行重写,以提供不同的时间戳生成逻辑。同样,NextPendingTask方法也是虚拟的,允许在子类中实现自定义的任务检索逻辑。

在Free Pascal环境下,结合 TPendingTaskList类的定义,我们可以编写一个示例程序来展示这两个类的基本用法。以下是一个简化的示例,一个 TPendingTaskList实例来按计划执行任务(在这个例子中,任务只是简单地打印消息)。

请注意,由于 TPendingTaskList可能是特定于某个库(如mORMot)的,因此您需要确保该库已被正确安装并包含在您的项目中。此外,为了简化示例,我们将在一个单线程环境中运行它,尽管这些类设计用于多线程环境。

program PendingTaskListExample;

{$MODE DELPHI}
{$APPTYPE CONSOLE}

uses
  SysUtils, // 包含WriteLn等标准输出函数
  YourSynapseUnit; // 替换为实际包含这些类定义的单元名称

var
  Queue: TSynQueue;
  TaskList: TPendingTaskList;
  I: Integer;
  TaskMessage: RawByteString;

begin
  try
    // 创建TPendingTaskList实例来按计划执行任务
    TaskList := TPendingTaskList.Create;
    try
      // 添加一些计划任务到列表中
      // 假设每个任务只是打印一条消息,延迟从当前时间开始计算
      TaskList.AddTask(1000, 'Task 1 in 1 second'); // 1秒后执行
      TaskList.AddTask(2000, 'Task 2 in 2 seconds'); // 2秒后执行

      // 注意:由于这个示例是在单线程环境中运行的,
      // 我们不会等待任务实际执行。在实际应用中,
      // 您可能需要在另一个线程中调用NextPendingTask,
      // 或者使用某种形式的定时器或事件循环来检查并执行任务。

      // 为了模拟任务执行,我们可以手动调用NextPendingTask
      // 并打印消息(但在实际应用中,这通常不是您想要的方式)
      repeat
        TaskMessage := TaskList.NextPendingTask;
        if TaskMessage <> '' then
          WriteLn('Executing Task: ', TaskMessage)
        else
          Break; // 没有更多待执行的任务,退出循环

        // 在这里,我们实际上应该等待一段时间再检查下一个任务,
        // 但为了简化示例,我们只是立即再次检查(这不是实际用法)
      until False;

    finally
      // 销毁TPendingTaskList实例(在这个简单的示例中可能不是必需的,
      // 但为了完整性而包含)
      TaskList.Free;
    end;

  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
  WriteLn('Program ended.');
end.

重要注意事项

  1. 单线程执行:上面的示例是在单线程环境中运行的,因此它不会按预期等待任务实际执行。在实际应用中,您应该在一个单独的线程中或在事件循环中定期调用 NextPendingTask来检查并执行任务。
  2. 模拟任务执行:为了简化示例,我们手动调用了 NextPendingTask并立即打印了消息。在实际应用中,您应该根据 NextPendingTask的返回值来决定是否执行任务,并且您可能需要等待一段时间再检查下一个任务。
  3. 替换单元名称:请确保将 'YourSynapseUnit'替换为实际包含 TSynQueueTPendingTaskList类定义的单元名称。
  4. 错误处理:示例中包含了基本的错误处理逻辑,但在实际应用中,您可能需要更详细的错误处理和日志记录。
  5. 线程安全:尽管 TSynQueueTPendingTaskList是线程安全的,但在从多个线程访问它们时,您仍然需要确保正确地同步对它们的访问(尽管在这个简单的示例中我们没有这样做)。在实际应用中,您可能需要使用锁、信号量或其他同步机制来确保线程安全。然而,在这个特定的示例中,由于我们是在单线程环境中运行,因此不需要担心线程安全问题。

标签:core,TSynQueue,示例,--,存储,mormot,队列,任务,线程
From: https://www.cnblogs.com/hieroly/p/18289754

相关文章

  • 7.8
    今天学习时间1小时代码半小时主要在vm里克隆了三个虚拟机并且实现主机用户之间的SSH免密登录互通还有每个主机的IP设置映射等等配置主机名映射:首先,修改/etc/hosts文件,将服务器和主机名进行映射。这样可以在各自服务器下通过主机名访问对应的IP地址。这一步是为了方便管理......
  • 力扣常用c++操作
    数字转字符串to_string()自定义sort函数sort(intervals.begin(),intervals.end(),[](vector<int>&v1,vector<int>&v2){returnv1[0]<v2[0];});自定义二分查找autoinsertit=lower_bound(intervals.begin(),intervals.end(),newInterval[0],......
  • centos7设置jdk为默认
    yumlistinstalled|grepjava(2)卸载自带的openJDKyum-yremoveR-javaR-java-develjava-1.7.0-openjdk.x86_64java-1.7.0-openjdk-devel.x86_64java-1.8.0-openjdk.x86_64java-1.8.0-openjdk-headless.x86_64tzdata-java.noarch(3)解压jdk文件并且放置在/usr/local/......
  • SpringBoot返回文件让前端下载的几种方式
    0x01背景在后端开发中,通常会有文件下载的需求,常用的解决方案有两种:不通过后端应用,直接使用nginx直接转发文件地址下载(适用于一些公开的文件,因为这里不需要授权)通过后端进行下载,同时进行一些业务处理本篇主要以方法2进行介绍,方法2的原理步骤如下:读取文件,得到文件的字节流......
  • 小流域设计洪水计算
        小流域通常是指集水面积不超过数百平方公里的小河小溪,但并无明确限制,一般认为流域面积在300~500km2以下可认为是小流域。从水文角度看小流域具有流域汇流以坡面汇流为主、水文资料缺乏、集水面积小等特性。小流域设计洪水计算,与大中流域相比,有许多特点,并且广泛应用于铁......
  • 变分自编码器(六):从几何视角来理解VAE的尝试
    前段时间公司组织技术分享,轮到笔者时,大家希望我讲讲VAE。鉴于之前笔者也写过变分自编码器系列,所以对笔者来说应该也不是特别难的事情,因此就答应了下来,后来仔细一想才觉得犯难:怎么讲才好呢? 对于VAE来说,之前笔者有两篇比较系统的介绍:《变分自编码器(一):原来是这么一回事》和《变分......
  • git-远程仓库
    1.添加远程仓库使用gitremoteadd命令将一个远程仓库添加到你的本地仓库中。gitremoteadd<name><url>例如:gitremoteaddoriginhttps://github.com/username/repository.git2.查看远程仓库使用gitremote命令可以查看当前配置的远程仓库。加上-v选项可以显示远程......
  • 一文带你了解什么是工控机?
    ​在工业计算领域,无风扇系统因其独特的设计和众多优势而获得了巨大的关注。与依靠风扇进行冷却的传统计算机不同,无风扇工业计算机经过精心设计,无需移动部件即可散热。在这篇文章中,我们将探讨无风扇工业计算机的引人注目的优势以及它们在各个行业中越来越受欢迎的原因。增强的可靠......
  • 变分自编码器(七):球面上的VAE(vMF-VAE)
    在《变分自编码器(五):VAE+BN=更好的VAE》中,我们讲到了NLP中训练VAE时常见的KL散度消失现象,并且提到了通过BN来使得KL散度项有一个正的下界,从而保证KL散度项不会消失。事实上,早在2018年的时候,就有类似思想的工作就被提出了,它们是通过在VAE中改用新的先验分布和后验分布,来使得KL散......
  • UniVAE:基于Transformer的单模型、多尺度的VAE模型
    大家都知道,Transformer的$\mathscr{O}(n^2)$复杂度是它的“硬伤”之一。不过凡事有弊亦有利,$\mathscr{O}(n^2)$的复杂度也为Transformer带来很大的折腾空间,我们可以灵活地定制不同的attentionmask,来设计出不同用途的Transformer模型来,比如UniLM、K-BERT等。本文介绍笔者构思的一......