UnityECS学习日记七:Unity Job System 之 IJob 多线程接口

上篇提到NativeContainer托管的数据类型,为Unity线程和job线程进行数据交换,它的内存分配有三种分配类型,当初始化时需要指定一个合适的分配器。有:

1.Allocator.Temp是最快的分配类型。它适用于分配一个生命周期只有一帧或更短时间的操作。你不应当把一个分配器为Temp类型分配的NativeContainer传递给jobs使用。你同时需要在函数返回之前调用Dispose方法(例如MonoBehaviour.Update,或者其他从原生到托管代码的掉用)。

2.Allocator.TempJob是相比于Temp一个较慢的分配类型,但它比Persistent要快。如果你在四帧内没有掉用Dispose,控制台会打印一个由原生代码生成的警告信息。绝大部分小jobs使用这种类型的NativeContainer分配器。

3.Allocator.Persistent是最慢的分配类型,但它可以持续存在到你需要的时间,如果必要的话可以贯穿应用程序的整个生命周期。它是直接调用malloc的一个封装。长时间的job可以使用这种分配类型。当性能比较紧张的时候你不应当使用Persistent。


IJob 作业系统的其中一个接口,其次还要知道:

1.JOB是一个一个的开线程任务,因为数据是顺序执行的所以它可以保证正确性。

2.如果想让线程人物真正的并行,那么可以采用IJobParallelFor。

3.一旦线程任务并行的话,意味着数据的执行顺序不是线性的,每一个Job里的数据不能完全依赖上一个Job执行后的结果。

4.[ReadOnly]声明数据只是制度,如果数据是只读的,意味着这个数据不需要枷锁。

5.如果不声明默认数据是Read/WriteDE的,数据一旦需要改写,那么Job就一定要等它。

6.然而这一切Unity都已经帮我们做好了,我们并不需要自己做枷锁解锁逻辑。


 

案例五:IJob多线程控制指定数量cube向上移动(Scne6)

脚本:IJobTest 

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

public class IJobTest : MonoBehaviour
{
    struct VelocityJob : IJob
    {
        public NativeArray<Vector3> positions;
        public NativeArray<Vector3> velocitys;
        public float delaTime;
        public void Execute()
        {
            for (int i = 0; i < positions.Length; i++)
            {
                positions[i] = positions[i] + velocitys[i] * delaTime;
            }
        }
    }
    public int gameCount = 300;
    public GameObject prefab;
    public GameObject[] gameObjs;
    void Start()
    {
        gameObjs = new GameObject[gameCount];
        for (int i = 0; i < gameCount; i++)
        {
            gameObjs[i] = Instantiate<GameObject>(prefab);
            gameObjs[i].transform.position = UnityEngine.Random.insideUnitSphere*40;
        }

    }


    void Update()
    {
        // 1.准备数据
        NativeArray<Vector3> tmpPositions = new NativeArray<Vector3>(gameCount, Allocator.TempJob);
        NativeArray<Vector3> tmpVelocitys = new NativeArray<Vector3>(gameCount, Allocator.TempJob);
        for (int i = 0; i < gameCount; i++)
        {
            tmpVelocitys[i] = new Vector3(0, 1, 0);
            //tmpPositions[i] = tmpPositions[i] + tmpVelocitys[i] * Time.deltaTime;
            tmpPositions[i] = gameObjs[i].transform.position;
        }
        VelocityJob job = new VelocityJob()
        {
            positions = tmpPositions,
            delaTime = Time.deltaTime,
            velocitys = tmpVelocitys
        };
        // 2.执行  
        //信号量 主线程如何知道子线程执行完毕
        JobHandle jobHandle = job.Schedule();

        // 3.同步
        jobHandle.Complete();

        for (int i = 0; i < gameCount; i++)
        {
            gameObjs[i].transform.position = tmpPositions[i];
        }


        tmpPositions.Dispose();
        tmpVelocitys.Dispose();
    }
}

 

如上图,通过预制体,动态生成了300个cube,而cube向上移动的数据,是通过多线程进行计算的。

 

 远程项目仓库(github):https://github.com/h1031464180/UnityECS

<think>嗯,用户想在Unity中查找Job多线程的错误并暂停执行。首先,我需要回忆一下Unity Job System的基本机制。Job System使用多线程来提升性能,但调试起来比较困难,因为多线程的问题通常难以复现。用户可能需要具体的调试方法,比如使用Log或断点,但在多线程环境下,这些方法可能不太有效。 然后,用户提到暂停执行,这可能是指在Job运行过程中暂停,但Job System本身是并发的,暂停可能会涉及到同步问题。我需要考虑Unity提供的工具,比如Profiler中的Jobs选项,或者使用Dependency来管理Job的执行顺序。 另外,用户可能遇到的数据竞争或内存问题,需要推荐使用Unity的安全系统,比如检查是否所有数据都使用了合适的容器,如NativeArray,并且是否正确标记了读写权限。还有,使用Debugger.Log可能在某些情况下帮助输出信息,但需要注意性能影响。 关于暂停,可能需要通过JobHandle.Complete来强制同步执行,或者在主线程中控制Job的调度。但暂停正在运行的Job可能不太现实,可能需要重新设计逻辑,比如分帧处理或者使用条件判断来暂停后续Job的调度。 用户提供的引用中提到了Unity Profiler用于内存分析,虽然主要是CPU Profiler,但可能对检测多线程问题有帮助。需要指导用户如何利用Profiler来查看Job的执行情况和线程状态。 总结一下,回答应该分步骤:调试方法、暂停机制、注意事项,并附上相关问题。确保使用正确的中文术语,比如“数据竞争”而不是“race condition”,同时保持LaTeX格式的正确应用。</think>### Unity Job System多线程调试与暂停方法 在Unity Job System中进行多线程调试需要特殊方法,以下是分步解决方案: #### 一、调试Job多线程错误 1. **使用`UnityEngine.Debug.Log`注入调试** ```csharp public struct MyJob : IJob { public NativeArray<float> data; public void Execute() { UnityEngine.Debug.Log("Job开始执行"); // 输出线程ID和状态 for(int i=0; i<data.Length; i++){ if(data[i] > 100) UnityEngine.Debug.LogError($"非法值出现在索引{i}"); // 条件断点模拟 data[i] *= 2; } } } ``` - 日志会显示在主线程控制台,但实际执行线程可能不同[^1] - 注意:频繁日志会影响性能,调试后需移除 2. **利用** `System.Diagnostics.Debugger` (仅Editor生效) ```csharp public void Execute() { System.Diagnostics.Debugger.Break(); // 强制触发调试中断 // 需要开启"Development Build"和"Script Debugging" } ``` 3. **使用Unity Profiler分析: - 打开`Window > Analysis > Profiler` - 选择`Jobs`视图查看线程状态和任务分配 - 检测Job间的依赖关系是否形成死锁 $$ \text{线程利用率} = \frac{\sum \text{有效执行时间}}{\text{总采样时间}} \times 100\% $$ #### 二、暂停Job执行机制 1. **通过Job依赖控制流程** ```csharp JobHandle jobHandle1 = job1.Schedule(); JobHandle jobHandle2 = job2.Schedule(jobHandle1); // 依赖job1 if(needPause){ jobHandle2.Complete(); // 强制同步执行到当前点 // 暂停期间不调度新Job } ``` 2. **使用原子操作标记** ```csharp public struct PausableJob : IJob { public NativeArray<bool> pauseFlag; public void Execute() { while(pauseFlag[0]) { System.Threading.Thread.Sleep(100); // 非实时系统可用 } // 实际工作代码 } } ``` #### 三、关键注意事项 1. **数据竞争检测:** - 所有共享数据必须使用`NativeArray`/`NativeList` - 检查读写权限标记: ```csharp [ReadOnly] public NativeArray<float> input; [WriteOnly] public NativeArray<float> output; ``` 2. **内存泄漏预防:** - 使用`using`块自动释放: ```csharp using(NativeArray<float> data = new NativeArray<float>(100, Allocator.TempJob)){ // Job代码 } ``` 3. **跨帧Job管理:** ```csharp void Update() { if(!jobHandle.IsCompleted) { this.enabled = false; // 暂停MonoBehaviour更新 jobHandle.Complete(); this.enabled = true; } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值