# 线程安全类型

> Understand Unity's custom thread-safe type, NativeContainer.

作业系统与 [Burst 编译器](https://docs.unity3d.com/Packages/com.unity.burst@latest/)搭配使用时效果最佳。由于 Burst 不支持托管对象，因此您需要使用非托管类型来访问作业中的数据。为此，您可以使用 [blittable 类型](https://learn.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types)，或使用 Unity 内置的 [`NativeContainer`](/engine/6000.0/script-reference/unity-collections-low-level-unsafe/native-container-attribute.md) 对象，这些对象对于本机内存而言是线程安全的 C# 封装器。`NativeContainer` 对象也允许作业访问与[主线程](/engine/6000.0/manual/scripting/optimization/job-system/overview.md#multithreading)共享的数据，而不是使用副本。

## NativeContainers 的类型

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

* [`NativeArray`](/engine/6000.0/script-reference/unity-collections/native-array-1.md)：非托管数组，会将原生内存缓冲区公开给托管代码。
* [`NativeSlice`](/engine/6000.0/script-reference/unity-collections/native-slice-1.md)：从特定位置获取到特定长度的 `NativeArray` 子集。

**注意：**[Collections 包](https://docs.unity3d.com/Packages/com.unity.collections@latest/)内含其他 `NativeContainer`。如需了解其他类型的完整列表，请参阅 Collections 文档的 [Collection 类型](https://docs.unity3d.com/Packages/com.unity.collections@latest/index.html?subfolder=/manual/collection-types.html)部分。

## 读写权限

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

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

```lang-cs
[ReadOnly]
public NativeArray<int> input;
```

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

## 内存分配

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

针对 `NativeContainer` 的内存分配和释放，存在三种[分配器](/engine/6000.0/script-reference/unity-collections/allocator.md)类型。对 `NativeContainer` 实例进行实例化时，必须指定相应的类型：

* `Allocator.Temp`：最快的分配。适用于生命周期为一帧或更短的分配。您不能使用 `Temp` 将分配传递给存储在作业成员字段中的 `NativeContainer` 实例。
* `Allocator.TempJob`：分配速度比 `Temp` 慢，但比 `Persistent` 快。适用于生命周期为四帧的线程安全分配。**重要信息**：您必须在四帧内对这种分配执行 `Dispose`，否则控制台会打印一条由原生代码生成的警告。大多数小型作业都使用这种分配类型。
* `Allocator.Persistent`：最慢的分配，但可以按需持续存在，如有必要，甚至可以在应用程序的整个生命周期内存在。它是对 [`malloc`](http://www.cplusplus.com/reference/cstdlib/malloc/) 的直接调用的封装器。持续时间较长的作业可以使用这种 NativeContainer 分配类型。当性能至关重要时，不要使用 `Persistent`。

例如：

```lang-cs
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);
```

\*\*注意：\*\*上例中的数字 1 表示 NativeArray 的大小。此例子中只有一个数组元素，因为在 result 中只存储了一段数据。

## NativeContainer 安全系统

所有 `NativeContainer` 实例中都内置了[安全系统](/engine/6000.0/manual/scripting/optimization/job-system/overview.md#safety-system)。安全系统会跟踪任何对 `NativeContainer` 实例的读取或写入，并使用这些信息对 `NativeContainer` 的使用强制执行某些规则，使其在多个作业和线程中以确定的方式运行。

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

如果要调度两项对同一 `NativeContainer` 实例进行写入的作业，可以调度[具有依赖项的作业](/engine/6000.0/manual/scripting/optimization/job-system/job-dependencies.md)。第一项作业会对 `NativeContainer` 进行项，执行完毕后，下一项作业就可以安全地对同一 `NativeContainer` 进行读取和写入。引入依赖关系可确保作业始终以一致的顺序执行，并确保 `NativeContainer` 中生成的数据具有确定性。

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

这些读写限制同样适用于从主线程访问数据的情况。例如，如果您尝试在某项对 NativeContainer 进行写入的作业完成前读取 NativeContainer 的内容，则安全系统会抛出错误。同样，如果您在仍有对 NativeContainer 进行读取或写入的待处理作业时尝试对 NativeContainer 进行写入，那么安全系统也会抛出错误。

此外，由于 NativeContainers 不实现 [`ref return`](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns)，您无法直接更改 `NativeContainer` 的内容。例如，`nativeArray[0]++;` 等同于写入 `var temp = nativeArray[0]; temp++;`，后者不会更新 `nativeArray` 中的值。

正确的做法是，必须将索引中的数据复制到本地临时副本中，修改该副本，然后将其存回。例如：

```lang-cs
MyStruct temp = myNativeArray[i];
temp.memberVariable = 0;
myNativeArray[i] = temp;
```

## 其他资源

* [实现自定义 `NativeContainer`](/engine/6000.0/manual/scripting/optimization/job-system/native-container/custom-nativecontainer.md)
