首页 > 编程语言 > 详细

Unity Job System

时间:2020-03-04 17:47:36      阅读:127      评论:0      收藏:0      [点我收藏+]

  参考链接 : 

  http://esprog.hatenablog.com/entry/2018/05/19/150313

  https://blogs.unity3d.com/2018/10/22/what-is-a-job-system/

 

  Job系统作为一个多线程系统, 它因为跟ECS有天生的融合关系所以比较重要的样子, 我也按照使用类型的分类来看看Job System到底怎么样.

  1. Pure Job System

  2. ECS on Job System

  Job说实话就是一套封装的多线程系统, 我相信所有开发人员都能自己封装一套, 所以Unity推出这个的时候跟着ECS一起推出, 因为单独推出来的话肯定推不动, 多线程, 线程安全, 线程锁, 线程共享资源, 这些都没什么区别, 我从一个修改顶点列表的功能来说吧.

  先来一个普通的多线程 :     

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System;
using System.Threading;

public class NormalListAccessTest01 : MonoBehaviour
{
    public class RunData
    {
        public List<Vector3> vecList = new List<Vector3>();
        public float speed;
        public float deltaTime;
    }

    public static void RunOnThread<T>(System.Action<T> call, T obj, System.Action endCall = null)
    {
        System.Threading.ThreadPool.QueueUserWorkItem((_obj) =>
        {
            call.Invoke(obj);
            if(endCall != null)
            { endCall.Invoke(); }
        });
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(100, 100, 100, 50), "Run Test"))
        {
            var data = new RunData();
            data.deltaTime = Time.deltaTime;
            data.speed = 100.0f;
            for(int i = 0; i < 10000; i++)
            {
                data.vecList.Add(UnityEngine.Random.insideUnitSphere * 50.0f);
            }
            RunOnThread<RunData>((_data) =>
            {
                Debug.Log("Start At : " + System.DateTime.Now.ToString("HH:mm:ss fff"));
                var move = _data.deltaTime * _data.speed;
                for(int i = 0; i < _data.vecList.Count; i++)
                {
                    var p = _data.vecList[i] + move * Vector3.right;
                    _data.vecList[i] = p;
                }
            }, data, () =>
            {
                Debug.Log(data.vecList[0]);
                Debug.Log("End At : " + System.DateTime.Now.ToString("HH:mm:ss fff"));
            });
        }
    }
}

 

  没有加什么锁, 简单运行没有问题, 下面来个Job的跑一下:  

using UnityEngine;
using Unity.Collections;
using Unity.Jobs;

public class JobSystemSample01 : MonoBehaviour
{
    struct VelocityJob : IJob
    {
        public NativeArray<Vector3> position;
        public float deltaTime;

        public void Execute()
        {
            for(var i = 0; i < position.Length; i++)
            {
                position[i] = position[i] + Vector3.right * deltaTime;
            }
        }
    }

    public void Test()
    {
        var position = new NativeArray<Vector3>(100, Allocator.Persistent);

        var job = new VelocityJob()
        {
            deltaTime = Time.deltaTime,
            position = position
        };

        JobHandle jobHandle = job.Schedule();
        JobHandle.ScheduleBatchedJobs();

        System.Threading.Thread.Sleep(3);

        Debug.Log(position[0]);     // Error : You must call JobHandle.Complete()
        jobHandle.Complete();
        Debug.Log(position[0]);

        position.Dispose();
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(100, 100, 100, 50), "Start Test"))
        {
            Test();
        }
    }
}

 

  这里就有一个大问题了, 在有注释的地方 // Error : You must call JobHandle.Complete(), 是说在Job没有调用Complete()时, 去获取相关数组内容是非法的! 而这个jobHandle.Complete(); 无法通过工作线程去调用, 也就是说Job的运行它是无法自行结束的, 无法发出运行结束的通知的, 对比上面封装的普通多线程弱爆了.  而这个Complete()函数如果在工作线程执行完成前调用, 会强制立即执行(文档也是写 Wait for the job to complete), 也就是说它只能在主线程调用并且会阻塞主线程, 这样就可以定性了, 它的Job System不是为了提供一般使用的多线程封装给我们用的, 

  经过几次测试, 几乎没有办法简单扩展Job系统来让它成为像上面一样拥有自动完成通知的系统, 尝试方法如下 : 

  1. 添加JobHandle变量到IJob中, 在Execute结束时调用  

        public NativeArray<Vector3> position;
        public float deltaTime;

        [Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
        public JobHandle selfHandle;

  报错, InvalidOperationException: VelocityJob.selfHandle.jobGroup uses unsafe Pointers which is not allowed. 无法解决

 

  2. 添加回调函数进去  

        public NativeArray<Vector3> position;
        public float deltaTime;

        public System.Action endCall;

  报错, Job系统的struct里面只能存在值类型!

 

  3. 使用全局的引用以及线程转换逻辑来做成自动回调的形式, 虽然可以使用了可是非常浪费资源 :  

using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
using System.Collections.Generic;

public class JobSystemSample01 : MonoBehaviour
{
    public static Dictionary<int, IJobCall> ms_handleRef = new Dictionary<int, IJobCall>();

    public class IJobCall
    {
        public JobHandle jobHandle;
        public System.Action endCall;
    }
    struct VelocityJob : IJob
    {
        public NativeArray<Vector3> position;
        public float deltaTime;

        public int refID;
        public void Execute()
        {
            for(var i = 0; i < position.Length; i++)
            {
                position[i] = position[i] + Vector3.right * deltaTime;
            }
            var handle = ms_handleRef[refID];
            ThreadMaster.Instance.CallFromMainThread(() =>
            {
                handle.jobHandle.Complete();
                if(handle.endCall != null)
                {
                    handle.endCall.Invoke();
                }
            });
        }
    }

    public void Test()
    {
        ThreadMaster.GetOrCreate();
        var position = new NativeArray<Vector3>(100, Allocator.Persistent);
        var job = new VelocityJob()
        {
            deltaTime = 1f,
            position = position,
            refID = 1,
        };
        ms_handleRef[1] = new IJobCall()
        {
            jobHandle = job.Schedule(),
            endCall = () => { Debug.Log(position[0]); position.Dispose(); }
        };
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(100, 100, 100, 50), "Start Test"))
        {
            Test();
        }
    }
}

  转换线程就用简单的回调 :  

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ThreadMaster : MonoBehaviour
{
    private static ThreadMaster _instance;
    public static ThreadMaster Instance
    {
        get
        {
            return GetOrCreate();
        }
    }

    private volatile List<System.Action> _calls = new List<System.Action>();

    public static ThreadMaster GetOrCreate()
    {
        if(_instance == false)
        {
            _instance = new GameObject("ThreadMaster").AddComponent<ThreadMaster>();
        }
        return _instance;
    }
    public void CallFromMainThread(System.Action call)
    {
        _calls.Add(call);
    }
    void Update()
    {
        if(_calls.Count > 0)
        {
            for(int i = 0; i < _calls.Count; i++)
            {
                var call = _calls[i];
                call.Invoke();
            }
            _calls.Clear();
        }
    }
}

  所以按照原理上来看, Job并不是为了一般化的多线程而做的简单封装, 反而它的逻辑相当复杂, 因为如果用在ECS系统上的话, 比如在计算人物骨骼动画, 蒙皮等数据的过程中, 到了渲染步骤, 它就必须在渲染之前强制完成所有计算来保证渲染的正确性吧, 它会把线程提到最高或者强制转换到主线程也有可能, 因为之前我用过转换上下文的一个框架(应该是使用Async语法代替协程的那个框架), 所以说它是为ECS服务的也不为过.

  通过上面封装就可以作为一般多线程使用了, 并且我们获得了引擎提供的数据安全和高效逻辑性, 再加上利用BurstCpmpile和只读属性, 能够提升一些计算效率吧. ECS on Job已经在另外一篇中说过了, 这里忽略了.

  

 

Unity Job System

原文:https://www.cnblogs.com/tiancaiwrk/p/12410825.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!