版本: Unity 6 (6000.0)
语言 : 英语
作业概述
实现自定义原生容器

线程安全类型

当您将作业系统与 Burst 编译器 一起使用时,作业系统的工作效果最佳。由于 Burst 不支持托管对象,因此您需要使用非托管类型来访问作业中的数据。您可以使用 可移植类型,或使用 Unity 内置的 NativeContainer 对象,它们是用于原生内存的线程安全 C# 包装器。NativeContainer 对象还允许作业访问与 主线程 共享的数据,而不是使用副本。

NativeContainers 类型

Unity.Collections 命名空间包含以下内置 NativeContainer 对象

  • NativeArray:非托管数组,它向托管代码公开原生内存的缓冲区
  • NativeSlice:从特定位置到特定长度获取 NativeArray 的子集。

注意:Collections 包 包含其他 NativeContainer。有关其他类型的完整列表,请参阅 Collections 文档 中的“集合类型”。

读写访问

默认情况下,当作业访问 NativeContainer 实例时,它同时具有读写访问权限。此配置可能会降低性能。这是因为作业系统不允许您同时调度访问 NativeContainer 实例的写访问权的作业以及写入该实例的另一个作业。

但是,如果作业不需要写入 NativeContainer 实例,您可以使用 [ReadOnly] 属性标记 NativeContainer,如下所示

[ReadOnly]
public NativeArray<int> input;

在上面的示例中,您可以同时执行作业以及其他对第一个 NativeArray 具有只读访问权限的作业。

内存分配器

创建 NativeContainer 实例时,您必须指定所需的内存分配类型。您使用的分配类型取决于您希望将原生容器保留多长时间。这样,您可以根据情况调整分配以获得最佳性能。

NativeContainer 内存分配和释放有三种 Allocator 类型。您必须在实例化 NativeContainer 实例时指定适当的类型

  • Allocator.Temp:最快的分配。将其用于生命周期为一帧或更短的分配。您不能使用 Temp 将分配传递到存储在作业成员字段中的 NativeContainer 实例。
  • Allocator.TempJob:比 Temp 慢,但比 Persistent 快。将其用于生命周期为四帧的线程安全分配。重要:您必须在四帧内对这种分配类型进行 Dispose 处理,否则控制台将打印从原生代码生成的警告。大多数小型作业都使用这种分配类型。
  • Allocator.Persistent:最慢的分配,但可以持续您需要的任何时间,如果需要,甚至可以持续整个应用程序的生命周期。它是对 malloc 的直接调用的包装器。较长的作业可以使用这种 NativeContainer 分配类型。不要在性能至关重要的场合使用 Persistent

例如

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

注意:上面示例中的数字 1 表示 NativeArray 的大小。在本例中,它只有一个数组元素,因为它在其结果中只存储一个数据。

NativeContainer 安全系统

所有 NativeContainer 实例都内置了 安全系统。它跟踪对任何 NativeContainer 实例的读写操作,并使用这些信息来强制执行 NativeContainer 使用的某些规则,使它们在多个作业和线程之间以确定性方式运行。

例如,如果两个独立的已调度作业写入同一个 NativeArray,则这是不安全的,因为您无法预测哪个作业先执行。这意味着您将不知道哪个作业会覆盖另一个作业的数据。当您调度第二个作业时,安全系统将抛出一个异常,并显示一个明确的错误消息,说明原因以及如何解决问题。

如果要调度两个写入同一个 NativeContainer 实例的作业,可以 使用依赖关系调度作业。第一个作业写入 NativeContainer,一旦它完成执行,下一个作业就可以安全地读写同一个 NativeContainer。引入依赖关系可以保证作业始终以一致的顺序执行,并且 NativeContainer 中的结果数据是确定性的。

安全系统允许多个作业并行从相同的数据读取。

这些读写限制也适用于从主线程访问数据。例如,如果您尝试在写入它的作业完成之前读取 NativeContainer 的内容,安全系统将抛出一个错误。同样,如果您尝试在仍有待处理的读取或写入它的作业时写入 NativeContainer,则安全系统也会抛出一个错误。

此外,由于 NativeContainers 没有实现 ref return,因此您无法直接更改 NativeContainer 的内容。例如,nativeArray[0]++; 与写入 var temp = nativeArray[0]; temp++; 相同,不会更新 nativeArray 中的值。

相反,您必须将数据从索引复制到本地临时副本中,修改该副本,然后将其保存回来。例如

MyStruct temp = myNativeArray[i];
temp.memberVariable = 0;
myNativeArray[i] = temp;

其他资源

作业概述
实现自定义原生容器