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(单线程)
Frame time: 16.7ms → Main thread bottleneck
✅ 使用 Job System(多线程)
帧时间降至约 5ms — 利用全部 CPU 核心
动手步骤
安装 Burst 包
Package Manager → 搜索 "Burst" → Install。Job System 自 Unity 2018+ 内置。添加 using Unity.Jobs、Unity.Collections。
用 IJob 写 Job 结构体
struct MyJob : IJob { public NativeArray
分配 NativeArray 并 Schedule
new NativeArray
Complete 并 Dispose
在 LateUpdate 中 handle.Complete()。从 NativeArray 读结果。array.Dispose() 释放非托管内存。
加上 [BurstCompile]
在结构体上放 [BurstCompile]——Jobs 菜单 → Open Inspector 看反汇编。Profiler 中 Burst 生效时显示绿色。
交互模拟器
对比计算速度:Main Thread vs Job System vs 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()