Skip to main content

使用任务局部变量

任务局部变量是循环一致的。它们只能由一个任务周期中的一个已定义任务写入,而所有其他任务只能读取它们。考虑到任务可以被其他任务打断,也可以同时运行。如果应用程序在具有多核处理器的系统上运行,则周期一致性首先也适用。

因此,当多个任务编辑相同的变量时,使用任务局部全局变量列表是一种自动获得同步(由编译器)的方法。使用普通 GVL 时情况并非如此。多个任务可以在一个周期内同时写入正常的 GVL 变量。

但是请注意,任务局部变量的同步相对耗时且占用大量内存,并不是每个应用程序都适合的方法。请参阅下面的更详细的技术信息和最佳实践建议,以帮助您做出决定。

在里面 CODESYS 项目, _cds_icon_gvl_tasklocal.png 全局变量列表(tasklocal) 对象可用于定义任务局部变量。从语法上讲,它对应于普通 GVL,但也包含对变量具有写访问权限的任务的信息。这样,在任务周期内,此类 GVL 中的所有变量都不会被其他任务更改。

提示

任务局部变量是复杂变量,其值不能在在线模式下使用 写入值 命令。

在下一节中,您将找到一个简单的示例,该示例展示了任务局部变量的原理和功能:有一个写入程序和一个读取程序。这些程序在不同的任务中运行,但访问任务局部全局变量列表中的相同数据,以便循环一致地处理它。

示例中显示的功能

可以在下面找到重新编程此示例应用程序的说明。

14. 示例应用程序
(* task-local GVL, object name: "Tasklocals" *)
VAR_GLOBAL
        g_diaData : ARRAY [0..99] OF DINT;
END_VAR

PROGRAM ReadData
VAR
        diIndex : DINT;
        bTest : BOOL;
        diValue : DINT;
END_VAR
bTest := TRUE;
diValue := TaskLocals.g_diaData[0];
FOR diIndex := 0 TO 99 DO
    bTest := bTest AND (diValue = Tasklocals.g_diaData[diIndex]);
END_FOR

PROGRAM WriteData
VAR
        diIndex : DINT;
        diCounter : DINT;
END_VAR
diCounter := diCounter + 1;
FOR diIndex := 0 TO 99 DO
        Tasklocals.g_diaData[diIndex] := diCounter;
END_FOR


节目 写入数据读取数据 由不同的任务调用。

节目中 WriteData 变成数组 g_diaData 充满价值观。该程序 ReadData 测试数组的值是否符合预期。如果是这种情况,则变量返回 bTest 因此 TRUE.

被测试的数组数据是关于变量的 g_diaData 在对象 Tasklocals 类型的 Globale Variablenliste (tasklokal) 宣布。这会同步编译器中的数据访问,并保证数据是循环一致的,即使访问程序是从不同的任务中调用的。在示例程序中,这特别意味着变量 test 在节目中 ReadData 总是 TRUE 是。

如果变量 g_diaData 在此示例中仅声明为全局变量列表 test ,即变量 test 在节目中 ReadData, 更多时候 FALSE 递送。因为在这种情况下,两个任务之一 FOR-Loop 可以被其他任务中断,或者两个任务可以同时运行(多核控制器)。因此,当读者阅读列表时,作者可以更改这些值。

申报限制

重要

更改任务局部变量列表中的声明后,无法在线更改应用程序。

声明全局任务局部变量列表时请注意以下事项:

  • 不要通过 AT 声明分配直接地址。

  • 不要映射到 PLC 配置中的本地任务变量。

  • 不要声明指针。

  • 不要声明引用。

  • 不要实例化功能块。

  • 不要同时声明任务局部变量 PERSISTENTRETAIN.

没有写权限的任务中的写访问被编译器报告为错误。但是,不可能确定所有侵犯写作权的地方。编译器只能将静态调用分配给任务。但是,例如,通过指针或接口调用功能块不会分配给任务。这意味着那里也不会记录任何写访问。此外,指针可以指向任务局部变量。通过这种方式,可以在读取任务中操作数据。在这种情况下,也不会发出运行时错误。但是,通过指针访问时更改的值不会复制回变量的公共引用中。

任务局部全局变量的属性和可能的行为

变量位于不同地址的每个任务的列表中。这意味着读取访问: ADR(variable name) 在每个任务中返回不同的地址。

同步机制保证以下内容:

  • 循环一致性

  • 免于锁定状态:任务在任何时候都不会等待另一个任务的动作。

但是,这种方法不能用于确定读任务一定会收到写任务副本的时间点。原则上,副本可以发散。在上面的例子中,不能假设每个写的副本都会被读者编辑一次。例如,读取任务可以处理同一个数组几个周期,或者数组的内容可以在两个周期之间“跳过”一个或多个值。两者都可能发生,必须加以考虑。

写任务可以在每个读任务对公共引用的两次访问之间保持一个周期。这意味着如果 n 读任务存在,写任务 n 周期被延迟到公共参考的下一次更新。

写入任务可以防止读取任务在每个周期中获得读取副本。因此,不可能指定读取任务肯定会收到副本的最大周期数。

特别是,当涉及运行速度非常慢的任务时,这可能会成为问题。假设任务仅每小时运行一次,然后无法访问任务局部变量,则该任务正在使用列表的非常旧的副本。因此,在任务局部变量中插入时间戳是有意义的,读取任务可以使用它来至少确定列表是否是最新的。您可以按如下方式添加时间戳:将类型变量添加到任务局部变量列表 LTIME 而在写作任务中,例如下面的代码: tasklocal.g_timestamp := LTIME();.

最佳实践

任务局部变量是为用例“单写者 - 多读者”设计的。在实现将由不同任务调用的代码时,使用任务局部变量非常有用。例如,上面描述的示例应用程序就是这种情况 appTasklocal 当它被几个读取任务扩展时,这些任务都访问相同的数组并使用相同的函数。

任务局部变量在具有多核处理器的系统上特别有用。您无法在这些系统上按优先级同步任务。然后需要其他同步机制。

如果阅读任务必须始终使用变量的最新副本,请不要使用任务局部变量。本地任务变量不适用于此。

类似的问题是“生产者-消费者”问题。当一个任务产生数据而另一个任务处理它时就是这种情况。有了这个星座,更喜欢不同类型的同步。例如,生产者可以使用一个标志来指示一个新的日期可用。消费者可以使用第二个标志来表明它已经处理了它的数据并且现在正在等待新的输入。两者都可以处理相同的数据。循环复制数据没有开销,消费者不会丢失生产者生成的任何数据。

监控

在运行时,内存中有几个可能不同的任务局部变量列表副本。但是,不是所有的值都可以在监控某个仓位的时候显示出来。因此,在线监控、监控列表和可视化中为任务局部变量显示来自公共参考的值。

如果设置断点,则会显示命中断点并因此停止的任务的数据。同时,其他任务继续运行。在某些情况下,可以更改通用副本。但是,在已停止任务的上下文中,值保持不变并按原样显示。你必须意识到这一点。

背景:技术实现

对于任务局部变量列表,编译器为每个任务创建一个副本,并为所有任务创建一个公共引用副本。创建一个包含与任务局部变量列表相同的变量的结构。还创建了一个具有这种结构的数组,并为每个任务创建了一个数组维度。因此为每个任务索引一个数组元素。如果现在在代码中访问列表的变量,则实际上访问的是列表的任务本地副本。此外,确定块当前在哪个任务中运行,并相应地对访问进行索引。

例如,代码行 diValue := TaskLocals.g_diaData[0]; 从上面的示例替换:

diValue := __TaskLocalVarsArray[__CURRENTTASK.TaskIndex].__g_diarr[0];

__CURRENTTASK 是一个运算符,ab CODESYS V3.5 SP13 可用于快速确定当前任务索引。

在运行时,任务本地列表的内容在写入任务结束时被复制到公共引用。在阅读任务的情况下,公共引用的内容在开始时被复制到任务本地副本。因此,对于 n 个任务,列表有 n+1 个副本:列表用作公共参考,此外,每个任务都有自己的列表副本。

调度程序控制多个任务的及时执行,从而控制任务切换。调度程序遵循的控制执行时间分配的策略旨在避免阻塞任务。因此,同步机制针对任务局部变量的属性进行了优化,从而避免了阻塞状态(锁定状态),并且一个任务永远不会等待另一个任务的动作。

同步策略:

  • 只要写入任务将副本写回共享引用,任何读取任务都不会获得副本。

  • 只要读取任务从共享引用中获取副本,写入任务就不会写回副本。

创建上述示例应用程序的说明

目标:你想从一个程序开始 ReadData 访问程序访问的相同数据 WriteData 要写。这两个程序应该在不同的任务中运行。您在任务局部变量列表中提供数据,以便以循环一致的方式自动处理它们。

要求:新创建并在编辑器中打开标准项目。

  1. 重命名应用程序 ApplicationappTasklocal 大约。

  2. 在下面添加 appTasklocal ST中的程序名称 ReadData 添加。

  3. 在下面添加 appTasklocal ST中的另一个程序名称 WriteData 添加。

  4. 命名默认任务 MainTask 在对象下 TaskkonfigurationRead 大约。

  5. 加入对话 配置 任务 Read 通过按钮 添加通话 调用程序 ReadData 添加。

  6. 粘贴在对象下 任务配置 添加了另一个名为 Write 并将程序调用添加到此任务 Write 添加。

    现在任务配置中有两个任务 WriteRead谁的节目 WriteData 分别 ReadData 称呼。

  7. 选择应用程序 appTasklocal 并添加一个类型的对象 全局变量列表(任务本地) 添加。

    对话 添加全局变量列表(任务本地)。 打开。

  8. 作为名称输入 Tasklocals 一。

  9. 从下拉列表中选择 具有写入权限的任务 任务 Write.

    _cds_img_tasklocal_objekcts.png

    在应用程序中使用本地任务变量的对象结构是完整的。您现在可以对上面示例描述中所示的对象进行编码。