Version: Unity 6.0 (6000.0)
语言 : 中文
Thread safe types
实现自定义原生容器

线程安全类型

作业系统与 Burst 编译器搭配使用时效果最佳。由于 Burst 不支持托管对象,因此您需要使用非托管类型来访问作业中的数据。为此,您可以使用 blittable 类型,或使用 Unity 内置的 NativeContainer 对象,这些对象对于本机内存而言是线程安全的 C# 封装器。NativeContainer 对象也允许作业访问与主线程共享的数据,而不是使用副本。

NativeContainers 的类型

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

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

注意:Collections 包内含其他 NativeContainer。如需了解其他类型的完整列表,请参阅 Collections 文档的 Collection 类型部分。

读写权限

默认情况下,如果某项作业能够访问 NativeContainer,那么该作业便具有读写权限。这种配置可能会降低性能。这是因为,当一项作业对 NativeContainer 实例进行写入时,作业系统不允许您再调度另一项拥有写入权限的作业。

但如果作业不需要对 NativeContainer 实例进行写入,那么您可以使用 [ReadOnly] 属性标记该 NativeContainer,如下所示:

[ReadOnly]
public NativeArray<int> input;

在上方示例中,您可以在多个作业对首个 NativeArray 拥有只读权限的同时执行该作业。

内存分配

创建 NativeContainer 实例时,必须指定所需的内存分配类型。使用的分配类型取决于您需要将原生容器保留多长时间的可用性。这样一来,您可以调整分配方案,从而确保在各种情况下都能获得最佳性能。

针对 NativeContainer 的内存分配和释放,存在三种分配器类型。对 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 的大小。此例子中只有一个数组元素,因为在 result 中只存储了一段数据。

NativeContainer 安全系统

所有 NativeContainer 实例中都内置了安全系统。安全系统会跟踪任何对 NativeContainer 实例的读取或写入,并使用这些信息对 NativeContainer 的使用强制执行某些规则,使其在多个作业和线程中以确定的方式运行。

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

如果要调度两项对同一 NativeContainer 实例进行写入的作业,可以调度具有依赖项的作业。第一项作业会对 NativeContainer 进行项,执行完毕后,下一项作业就可以安全地对同一 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;

其他资源

Thread safe types
实现自定义原生容器