Unity扫盲:unity可以开多线程吗?

在很多人的印象中,都觉得unity不支持多线程,因为里面有一个协程的概念,认为协程就是因为unity不支持多线程而用的一个功能,其实这是不对的,unity是可以开多线程的,只是一般不去开线程,为什么呢?
因为游戏大多数都是单线程,而且有的游戏对于GPU的要求比较高,大多数游戏考验的是电脑CPU的单核能力,和GPU的运算能力,所以配置一台游戏主机的时候,没必要去看多核计算能力.你买的多核CPU在大多数情况下,都是单核在跑游戏逻辑.

请添加图片描述
现在就在unity中开启多线程的功能,代码简单,不必细说:

using UnityEngine;
using System.Threading;
public class testScripts : MonoBehaviour
{
    Thread t1;
    Thread t2;

    void Start()
    {
        t1 = new Thread(T1);
        t2 = new Thread(T2);
        t1.Start();
        t2.Start();
    }

    void T1()
    {
        Debug.Log("1");
    }

    void T2()
    {
        Debug.Log("2");
    }

}

运行之后会有以下2种情况:

在这里插入图片描述
在这里插入图片描述
那么为什么会有哲两种情况呢?因为线程是平行运行的,也就是说程序在开线程的时候,你的这几个线程一定是平行的关系.谁的运气好,谁就先运行,下面用一个例子来说明,这个例子的功能就是用2个线程去操作一个字符串:

using UnityEngine;
using System.Threading;
public class testScripts : MonoBehaviour
{
    Thread t1;
    Thread t2;
    string str = "";
    void Start()
    {
        t1 = new Thread(T1);
        t1.Start();
        t2 = new Thread(T2);
        t2.Start();
        Thread.Sleep();
        Debug.Log(str);
    }

    void T1()
    {
        for (int i = ; i < ; i++)
        {
            Thread.Sleep();
            str += 'a';
        }
    }

    void T2()
    {
        for (int i = ; i < ; i++)
        {
            Thread.Sleep();
            str += 'b';
        }
    }

}

由于两个线程并行执行,字符串str时而被T1操作,时而被T2操作,所以打印如下结果:
在这里插入图片描述
这个称之为"线程互斥"

为了避免线程竞争,可以通过加锁的方式(Lock)来处理,当2个线程争夺一个锁的时候,只能有一个线程有资格获取,另一个挂起等待。一直等到可用为止,这种情况,保证在同一时刻只有一个线程能进入临界区。程序改为下面的状态:

using UnityEngine;
using System.Threading;
public class testScripts : MonoBehaviour
{
    Thread t1;
    Thread t2;
    string str = "";
    void Start()
    {
        t1 = new Thread(T1);
        t1.Start();
        t2 = new Thread(T2);
        t2.Start();
        Thread.Sleep();
        Debug.Log(str);
    }

    void T1()
    {
        lock (str)
        {
            for (int i = ; i < ; i++)
            {
                Thread.Sleep();
                str += 'a';
            }
        }
    }

    void T2()
    {
        lock (str)
        {
            for (int i = ; i < ; i++)
            {
                Thread.Sleep();
                str += 'b';
            }
        }
    }

}

运行结果:

在这里插入图片描述
那么看到这里,你应该知道,unity里面是可以开多线程的了,那么为什么这么简单的东西却有很多人相信unity不能开多线程呢?因为在unity里面,不支持在新开的线程中操作UnityEngine SDK.考虑到苹果手机的单线程系统,除此之外还有其他限制,所以为了简单的教学直接就告诉别人,unity不能开多线程-_-||.其实这个是不对的.在有一些大场景加载中.或者数据比对中,开启多线程还是很有必要的(考虑到适用的场景).

如果直接在新线程中操作了UnityEngine SDK,这么用了,unity会报一个错:

can only be called from the main thread.

也就是说,你只能在主线程中操作游戏物体.所有在UnityEngin命名空间下的所有声明方法,类,皆不能在unity规定的线程之外调用它.

那么为什么要这么做呢?

游戏中逻辑更新和画面更新的时间点要求有确定性,必须按照帧序列严格保持同步,否则就会出现游戏中的对象不同步的现象.那么多线程可以吗?上文中提到的线程互斥还记得吗?那保不齐会出现什么鬼畜的动作.

其实在这里,只能说unity中不建议开启多线程,只是要考虑到平台,比如你开发一个端游,因为多线程确实不安全,如果你是做苹果端游戏,苹果就是一个单线程系统,做的假后台.你如果在苹果系统中开了新线程,那闪退是必不可少的了.

unity为了弥补开多线程的风险,unity提供了一个叫协程的功能.协程与线程的功能几乎都是一样的.协程也可以在运行期间同步执行某个代码块.

但是它们二者是有区别的,图示如下:

在这里插入图片描述
从图中可以看出,线程与unity主线程之间是平级关系,假如线程1直接去操作unity主线程正在操作的对象,举个例子,主线程正在控制玩家向前移动,而线程1却要控制玩家准备战斗,那么这时候,玩家该执行什么动作呢?通过线程互斥知道如果没有,如果你电脑是多核的,那么这里多线程就跑的快

在这里插入图片描述
协程是跑在主线程中的,这个就涉及到脚本的生命周期了

脚本继承MonoBehavior对象,会在OnStart()中做地图初始化,在Update()中进行帧刷新,然后MonoBehavior脚本可以作为一个Component挂载到某个GameObject中。

这一点点入门级的理解大致能在游戏中把地图引擎跑起来,但是并没有深入的去理解这些框架函数的执行原理。

在这里插入图片描述
首先,unity引擎没绘制一帧图像都要跑一遍生命周期,那么协程在里面是怎么跑的呢?

首先,协程是什么呢?总体来说,对与Unity,它是单线程的设计,它更倾向使用time slicing(时间分片)的协程(coroutine)去完成异步任务,融合到了刚刚提到的生命周期中。

要理解协程,先回顾下线程:线程是操作系统级别的概念,现代操作系统都实现并且支持线程,线程的调度对应用开发者是透明的,开发者无法预期某线程在何时被调度执行。基于此,一般那种随机出现的BUG,多与线程调度相关。

而协程Coroutine是编译器级的,本质还是一个线程时间分片去执行代码段。它通过引擎相关的代码使得代码段能够实现分段式的执行,显式调用 yield函数后才被挂起,重新开始的地方是yield关键字指定的,一次一定会跑到一个yield对应的地方。因为协程本质上还是在主线程里执行的,需 要内部有一个类似栈的数据结构,当该coroutine被挂起时要保存该coroutine的数据现场以便恢复执行。

从这点上来看,这个有点像回掉函数.当yield条件为满足时候,会一直运行协程里面的代码块.

在Unity3D中,协程是可自行停止运行 (yield),直到给定的 YieldInstruction 结束再继续运行的函数。这就好比这个条件后天性的给你一个满足.立马跳出循环.

协程 (Coroutines) 的不同用途:

yield; 在下一帧上调用所有 Update 函数后,协同程序将继续运行。

yield WaitForSeconds(2); 在指定的时间延迟之后,为此帧调用所有 Update 函数之后继续运行

yield WaitForFixedUpdate(); 在所有脚本上调用所有 FixedUpdate 后继续运行

yield WWW 完成 WWW 下载后继续运行。

yield StartCoroutine(MyFunc); 连接协同程序,并等待 MyFunc coroutine 首先结束。
也就是说,将代码段分散在不同的帧中,每次执行一段,下一帧再执行yield挂起的地方。这样看起来,就是多线程的效果.

到文末,一句话总结:

unity可以开多线程,但是不建议使用,建议使用协程.

那么,我们提到过,加载资源当然使用多线程了那么留个问题:比如程序中的Resource.Load();函数,那么这个函数加载也是在unity主线程中加载的吗?

猜你喜欢

转载自blog.csdn.net/weixin_41590778/article/details/129527679