Unity Term Book
进阶技术

Job System & Burst

Unity 的 C# Job System 让你编写安全的多线程代码,搭配 Burst Compiler 可用原生 SIMD 极快地执行重计算任务。适合大量 AI 更新、物理近似与程序化生成等场景,帮助性能敏感项目把 CPU 密集工作移出主线程,同时避免常见的数据竞争、竞态条件与线程安全隐患等问题。

想象一下...

Main Thread 是一位厨师包办所有活。Job System 是流水线厨房:每条工作线程并行做独立任务(切、煮、炸)。主厨只调度并收结果。Burst Compiler 把每把厨刀换成数控铣床——把 C# 变成 SIMD 汇编,快 10–100 倍。

概念详解

C# Job System:出于线程安全,Unity 不允许其他线程碰 UnityEngine 对象(Transform、MonoBehaviour)。Job System 的规则:数据放在 NativeArray<T> (非托管内存,可跨线程共享)。Job 结构体实现 IJob、 IJobParallelFor,或 IJobParallelForTransform。Schedule → 在工作线程运行 → 读结果前 Complete。

Burst Compiler 是 LLVM 编译器,把 IL 编译成带 SIMD (Single Instruction Multiple Data)的高性能原生代码——一次处理多个 float(SSE2、AVX2、NEON)。 在 job 结构体上加 [BurstCompile]。 Burst 不支持托管对象、异常或引用类型——只有值类型与指针。

NativeArray<T> 是非托管内存:无 GC 开销, 可在多线程读写(有约束)。用完必须 Dispose() 否则泄漏。Editor 中的 NativeContainer safety checks 会自动抓住竞态。

工作流:Schedule() 入队 job;Complete() 在读结果前等待 (阻塞)。用 dependency chains(把 JobHandle 传给下一次 Schedule()) 串联 job,无需提前 Complete。ECS + DOTS 完全建立在 Job System + Burst 上——这正是 ECS 性能高的原因。

线程模型:单线程 vs Job System

❌ 不使用 Jobs(单线程)

Main
Physics → AI → Render → Input → ...
Worker 1
idle idle idle idle...
Worker 2
idle idle idle idle...
Worker 3
idle idle idle idle...

Frame time: 16.7ms → Main thread bottleneck

✅ 使用 Job System(多线程)

Main
Input+Render
Complete+Apply
Worker 1
Physics Batch Job
Worker 2
AI Pathfinding Job
Worker 3
Procedural Mesh Job

帧时间降至约 5ms — 利用全部 CPU 核心

动手步骤

1

安装 Burst 包

Package Manager → 搜索 "Burst" → Install。Job System 自 Unity 2018+ 内置。添加 using Unity.Jobs、Unity.Collections。

2

用 IJob 写 Job 结构体

struct MyJob : IJob { public NativeArray data; public void Execute() { ... } }——无 MonoBehaviour,无引用类型。

3

分配 NativeArray 并 Schedule

new NativeArray(count, Allocator.TempJob) → new MyJob { data = array } → .Schedule() → 保留 JobHandle。

4

Complete 并 Dispose

在 LateUpdate 中 handle.Complete()。从 NativeArray 读结果。array.Dispose() 释放非托管内存。

5

加上 [BurstCompile]

在结构体上放 [BurstCompile]——Jobs 菜单 → Open Inspector 看反汇编。Profiler 中 Burst 生效时显示绿色。

交互模拟器

对比计算速度:Main Thread vs Job System vs Job + Burst。

Main Thread
--
Job System
--
Job + Burst
--

按下 Run 进行基准测试。(基于 Unity 实测的模拟,并非实时测量)

代码示例

基础

IJobParallelFor——跨线程并行处理 N 个元素。

using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
using Unity.Mathematics;
using UnityEngine;

// [BurstCompile] → compile to SIMD native code
[BurstCompile]
struct CalculatePositionsJob : IJobParallelFor
{
  [ReadOnly] public NativeArray<float3> velocities;
  public NativeArray<float3> positions; // Write results here
  public float deltaTime;

  // Execute runs in parallel for each index
  public void Execute(int index)
  {
      positions[index] += velocities[index] * deltaTime;
  }
}

public class FlockSimulation : MonoBehaviour
{
  const int COUNT = 10000;
  NativeArray<float3> positions, velocities;
  JobHandle jobHandle;

  void Awake()
  {
      positions  = new NativeArray<float3>(COUNT, Allocator.Persistent);
      velocities = new NativeArray<float3>(COUNT, Allocator.Persistent);
  }

  void Update()
  {
      jobHandle.Complete(); // Wait for last frame's job
      jobHandle = new CalculatePositionsJob
      {
          velocities = velocities,
          positions  = positions,
          deltaTime  = Time.deltaTime
      }.Schedule(COUNT, 64); // 64 = batch size per thread
  }

  void OnDestroy()
  {
      jobHandle.Complete();
      positions.Dispose();
      velocities.Dispose();
  }
}

代码示例

进阶

Job 依赖链——多个 job 串联,中间无需 Complete()。

using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;

// Two jobs in sequence via a dependency — main thread is not blocked in between
void ScheduleChain(NativeArray<float> data)
{
  // Job 1: square each value
  var squareJob = new SquareJob { data = data };
  JobHandle squareHandle = squareJob.Schedule(data.Length, 32);

  // Job 2: needs job 1 first → pass the handle as a dependency
  var sumJob = new SumJob { data = data };
  JobHandle sumHandle = sumJob.Schedule(squareHandle); // dependency!

  // Main thread is not blocked. The chain runs in parallel with rendering...
  // ...until LateUpdate needs the result:
  sumHandle.Complete();
  Debug.Log($"Sum of squares: {sumJob.result[0]}");
}

// Several jobs in parallel, then merge:
void ScheduleParallel()
{
  var jobA = new JobA().Schedule();
  var jobB = new JobB().Schedule();
  // CombineDependencies: wait for BOTH before jobC
  var combined = JobHandle.CombineDependencies(jobA, jobB);
  var jobC = new JobC().Schedule(combined);
  jobC.Complete();
}

📌 快速记忆

  • ▸Job 是 struct——不能是 class/托管类型
  • ▸NativeArray:非托管,用完必须 Dispose()
  • ▸[BurstCompile] → SIMD 原生代码,快 10–100 倍
  • ▸依赖链:中间不要 Complete()
  • ▸Allocator.TempJob:最多 4 帧;Persistent:长期存活

⚠️ 常见错误

  • ❌ NativeArray not disposed warning

    忘了 Dispose() → 原生泄漏,GC 无法回收

    ✅ 在 OnDestroy 中 Dispose,或对 Allocator.TempJob 用 using

  • ❌ InvalidOperationException: The previously scheduled job ... writes...

    Complete() 前在主线程读 NativeArray

    ✅ 主线程碰 NativeArray 前先调用 handle.Complete()