JobSystem 在旧版本中仅供 Unity 内部 C++ native 层使用。自 Unity 2018 起,该功能向 managed 层(C#)开放,配合 Burst 编译器,开发者可以在 C# 层更轻松地编写多线程代码。

官方指引文档:https://docs.unity3d.com/Manual/JobSystem.html

JobSystem 的核心设计思路:

  1. 通过拷贝和只读等手段避免数据竞争。
  2. 引入 NativeContainer,提供多线程数据交互的解决方案。

A ParallelFor job dividing batches across cores

JobSystem 注意事项

详见官方文档:https://docs.unity3d.com/Manual/JobSystemTroubleshooting.html

  1. Do not access static data from a job
  2. Flush scheduled batches — 由于调度是在主线程发起的,如果在循环中大量调用 Schedule,已被调度的 Job 需等待循环结束才能执行。若主线程存在大量计算,反而会增加延迟。可使用 JobHandle.ScheduleBatchedJobs 立即触发执行,无需等到 Complete 调用。
  3. Don't try to update NativeContainer contents
  4. Call JobHandle.Complete to regain ownership
  5. Use Schedule and Complete in the main thread
  6. Use Schedule and Complete at the right time
  7. Mark NativeContainer types as read-only
  8. Do not allocate managed memory in jobs

JobSystem 性能提升示例

开启 JobSystem 后,帧率稳定在 60 FPS 左右(CPU 使用率 68%);关闭后帧率降至 20 FPS 左右(CPU 使用率 13%)。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Collections;
using Unity.Transforms;
using UnityEngine.Jobs;

public struct Job3 : IJobParallelForTransform
{
    //   public NativeArray<int> result;//ref
    //  public Job3()//NativeArray<int> result)
    //  {
    //    this.result = result;
    //  }
    public void Execute(int index, TransformAccess transform)
    {
        //calc operation
        {
            var rot = Quaternion.LookRotation(Vector3.forward);
            for (int i = 0; i < 256; i++)
            {
                rot = Quaternion.LookRotation(Vector3.forward);
            }
            transform.rotation = Quaternion.LookRotation(Vector3.forward);
            transform.position = Vector3.one;
        }
        //   Debug.LogError(System.Threading.Thread.CurrentThread.ManagedThreadId + "  " + index);
    }
}


public class Test1 : MonoBehaviour
{
    public List<GameObject> _list = new List<GameObject>();
    public GameObject prototype;
    TransformAccessArray result;
    void t3()
    {
        //use job system
        var j1 = new Job3();
        var handle = j1.Schedule(result);
        //wait for job done
        handle.Complete();

        //do in main thread
        /*  foreach (var p in _list)
          {
              p.transform.position += Vector3.one;
              p.transform.rotation = Quaternion.LookRotation(Vector3.forward);
              var rot = Quaternion.LookRotation(Vector3.forward);
              for (int i = 0; i < 256; i++)
              {
                  rot = Quaternion.LookRotation(Vector3.forward);
              }
          }*/
    }

    void Start()
    {
        const int len = 1024;
        TransformAccessArray.Allocate(len, len, out result);
        for (int i = 0; i < len; i++)
        {
            _list.Add(GameObject.Instantiate<GameObject>(prototype));
            result.Add(_list[i].transform);
        }
    }

    void Update()
    {
        t3();
    }
}