Unity协程和线程的区别深入理解(附实验展示)

news2024/11/27 18:46:00

Unity协程和线程的区别附实验展示

  • 写在前面
    • 协程、进程、线程的概念
    • 进程与线程的区别
    • 协程与线程的区别
    • 实验1:协程中执行普通函数
    • 实验2:协程中开启另一个协程
    • 实验3:协程中开启WWW请求
    • 实验4:一个脚本中多个协程访问临界资源
    • 实验5:线程中访问临界资源
    • 多线程实验:协程中开启异步操作
    • 实验6:多脚本开协程去访问临界资源
  • 写在后面

写在前面

前几天被问到线程和协程的区别,发现网上主要是从理论上来讲两者的区别理解起来很抽象,所以我下来做了几个实验来体现线程与协程的区别。
测试代码见:https://github.com/hahahappyboy/UnityProjects/tree/main/%E5%8D%8F%E7%A8%8B%E7%BA%BF%E7%A8%8B%E5%8C%BA%E5%88%AB%E6%B5%8B%E8%AF%95/Assets

协程、进程、线程的概念

进程: 是系统进行资源分配的基本单位,进程是线程的容器。打开unity的程序就是开始了一个进程,每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大。
线程: 是操作系统能够进行资源调度的最小单位(即CPU调度和分派的基本单位),是进程中实际运作的单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的公共资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少。
注意1:属于同一进程的多个线程之间的切换不会引起进程的切换,只有属于不同进程的线程之间的切换才会引起进程的切换。例如QQ间线程的切换(换一个人聊天)不会引起进程的切换,而QQ线程切换为音乐线程会引起进程的切换
注意2:CPU时间片是直接分配给线程的,线程拿到CPU时间片就能执行了,CPU时间片不是先分给进程然后再由进程分给进程下的线程的。所有的进程并行,线程并行都是看起来是并行,其实都是CPU片轮换使用。线程分到了CPU时间片,就可以认为这个线程所属的进程在运行,这样就看起来是进程并行。

协程: 具有多返回点的方法,把时间分片,协程也是运行在Unity主线程里的,是主线程的一部分,Unity只有主线程有些运算不希望放在同一帧里取运算,在同一帧里会卡顿,就可以用协程把他分散不同的帧里运算,这样就可以有一个流畅的体验。将协程代码中由yield return 语句分割的部分分配到每一帧执行。
在这里插入图片描述在这里插入图片描述

进程与线程的区别

进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
线程是处理器调度的基本单位,但进程不是

协程与线程的区别

协程Coroutine——”伪异步“, 协程不是线程,也不是异步执行的,协程只是把yield return之间的语句分割的部分分配到每一帧执行 (实验1、2)。就和monobehaviour的update函数一样也是在脚本生命周期里执行,属于生命周期的一部分。即unity在每一帧都会处理对象上的协程,也就是说,协程跟update一样都是unity每帧会去处理的函数(update之前,lateupdate之后),所以协程不是异步。除非你在协程里面去开启异步,如网络请求WWW ,才能让协程去创建线程实现异步 (实验3、5)
线程Thread——“真异步 ”, 开启线程后线程和脚本生命周期没有关系了属于两条并行的逻辑,你做你的我做我的。但子线程内不可访问游戏对象或者组件以及相关的Unity API。即unity可以开多线程,但是不能操作unity的api,如调用transform。

在Unity的主线程里只会有一个处于运行状态的协程, 即便不同脚本开启多个协程或则同一脚本开启多给协程也是一个一个顺序执行 (实验4、6),Unity中有个协程调度器,里面有个协程列表协程调度器按照协程列表去执行,从而保证主线程里只能有一个处于运行状态的协程
所以协程之间不会出现临界区资源访问的问题,而但是你开多线程去访问临界资源就会出现问题,因为你没办法控制这个线程会执行到什么时候转换为下一个线程(因为线程是在CPU循环占用的)。 (多线程实验)

实验1:协程中执行普通函数

代码:
在协程里用一个for循环模仿10个耗时操作

public class 协程测试1 : MonoBehaviour
{
    void Start(){
        Debug.Log("Start开始");
        StartCoroutine(协程1());//开启协程
        Debug.Log("Start结束");

    }
    IEnumerator 协程1(){
        for (int i = 0; i < 10; i++) //循环C
        {
            yield return 耗时操作1(); //协程1
        }
    }
    public int 耗时操作1(){
        Debug.Log("耗时操作开始");
        for (int i = 0; i < 10000; i++){
        }
        Debug.Log("耗时操作结束");

        return 1;
    }
    // 更新数据
    private int frame = 0;
    void Update(){
        frame++;
        Debug.Log("第"+frame+"帧的"+"Update");
    }
    //晚于更新
    void LateUpdate(){
        Debug.Log("第"+frame+"帧的"+"LateUpdate");
    }
}

结果
可以看到,耗时操作执行完成后Unity才会执行接下来的生命周期函数,也就说Unity只是把10个耗时操作通过yield return分在了10帧执行,而不是1帧。但是协程并没有脱离脚本的生命周期,也是在生命周期中每帧执行(Update之后LateUpdate之前)。
在这里插入图片描述

实验2:协程中开启另一个协程

代码: 在协程里再开个协程执行,去执行耗时操作

public class 协程测试2 : MonoBehaviour
{
    void Start(){
        Debug.Log("Start开始");
        StartCoroutine(协程1());//开启协程
        Debug.Log("Start结束");

    }
    IEnumerator 协程1(){
        Debug.Log("协程开始");
        for (int i = 0; i < 10; i++) //循环C
        {
            yield return StartCoroutine(耗时操作1()); //协程1
        }
        Debug.Log("协程开始");
    }
    IEnumerator 耗时操作1(){
        Debug.Log("耗时操作开始");
        for (int i = 0; i < 10000; i++){
        }
        Debug.Log("耗时操作结束");
        yield return null;
    }
    // 更新数据
    private int frame = 0;
    void Update(){
        frame++;
        Debug.Log("第"+frame+"帧的"+"Update");
    }
    //晚于更新
    void LateUpdate(){
        Debug.Log("第"+frame+"帧的"+"LateUpdate");
    }
}

结果
可以看到与实验1并没有什么区别,协程会等开启的协程的yield return执行完成后,才执行下面的生命周期函数。也是每帧的执行,没有脱离生命周期。
在这里插入图片描述

实验3:协程中开启WWW请求

代码: 在协程里开启WWW请求

public class 协程测试3 : MonoBehaviour
{
    void Start(){
        Debug.Log("Start开始");
        StartCoroutine(协程1());//开启协程
        Debug.Log("Start结束");
    }
    IEnumerator 协程1(){
        Debug.Log("协程开始");
        for (int i = 0; i < 10; i++) //循环C
        {
            Debug.Log("耗时操作开始");
            UnityWebRequest www = UnityWebRequest.Get("www.baidu.com");
            yield  return www.SendWebRequest();
            Debug.Log("耗时操作结束");
        }
        Debug.Log("协程结束");
    }
 
    // 更新数据
    private int frame = 0;
    void Update(){
        frame++;
        Debug.Log("第"+frame+"帧的"+"Update");
    }
    //晚于更新
    void LateUpdate(){
        Debug.Log("第"+frame+"帧的"+"LateUpdate");
    }
}

结果
可以看到第1次www操作第1帧开始,第2帧lateupdate后才结束。第2次www操作第2帧lateupdate后开始,第9帧才结束。这才是真正的异步,原因是SendWebRequest函数本身就是真异步操作,是单独开一个线程来向服务器请求数据(多线程模式),从而让该函数脱离了Unity生命周期的循环,只有在该函数执行完后才会回到yield return的地方,继续往下执行,在这期间协程会每帧都检测该函数是否执行完毕。
在这里插入图片描述
在这里插入图片描述

实验4:一个脚本中多个协程访问临界资源

代码: 在线程中访问临界资源

public class 协程测试4 : MonoBehaviour
{
    private static int n = 5;
    void Start(){
        for (int i = 0; i < 10; i++) {
            StartCoroutine(协程1(i));//开启协程
        }
    }
    IEnumerator 协程1(int id){
        if (n==5) {
            n++;
            Debug.Log("id="+id+" n="+n);
        }
        n = 5;
        yield return null;
        Debug.Log("id=" + id + "执行完毕");
    }
    // 更新数据
    private int frame = 0;
    void Update(){
        frame++;
        Debug.Log("第"+frame+"帧的"+"Update");
    }
    //晚于更新
    void LateUpdate(){
        Debug.Log("第"+frame+"帧的"+"LateUpdate");
    }
}

结果
可以看出即便开多个协程,但在同一个monobehavior脚本里同一时间只会有一个协程在执行,原因很简单因为协程就是在生命周期里执行的,生命周期是顺序执行的,那么开启的协程也是按顺序执行的。
在这里插入图片描述

实验5:线程中访问临界资源

代码: 线程中访问临界资源

public class 线程 {
    // private static object lockObject = new object();
    private static int n = 5;
    public static void Main() {
        for (int i = 0; i < 10; i++) {
            new Thread(ThreadMain).Start();
        }
    }
     public static void ThreadMain() {
         // lock (lockObject) {
             if (n==5) {
                 n++;
                 Console.WriteLine("n="+n);
             }
             n = 5;
         // }     
     }
}

结果
可以看到在不加锁的情况下,因为线程是异步的,其中有线程执行n++后还没打印,CPU就换为其他线程执行了n=5,才导致打印了5
在这里插入图片描述

多线程实验:协程中开启异步操作

代码: 协程中开启异步操作

public class 协程测试5 : MonoBehaviour
{
    void Start(){
        Debug.Log("Start开始");
        StartCoroutine(协程1());//开启协程
        Debug.Log("Start结束");

    }
    IEnumerator 协程1(){
      
       yield return 耗时操作1(); //协程1
      
    }
    private async Task<int> 耗时操作1()
    {
        return await Task.Run( () => {
            Debug.Log("耗时操作开始");
            for (int i = 0; i < 10000000; i++) //循环B
            {
            }
            Debug.Log("耗时操作结束");
            return 1;
        });
    }
    // 更新数据
    private int frame = 0;
    void Update(){
        frame++;
        Debug.Log("第"+frame+"帧的"+"Update");
    }
    //晚于更新
    void LateUpdate(){
        Debug.Log("第"+frame+"帧的"+"LateUpdate");
    }
}

结果
可以看到在协程中开启异步后,过了两帧才执行完,而不想在协程中开启协程那种每帧去执行。因为异步是开线程执行,此时已经脱离了主线程。
在这里插入图片描述

实验6:多脚本开协程去访问临界资源

代码: 多脚本开协程去访问临界资源

public static class  临界资源 {
        public static int n = 5;
        public static void ThreadMain() {
                if (n==5) {
                        n++;
                        Debug.Log("n="+n);
                }
                n = 5;
        }
}

public class 协程测试6 : MonoBehaviour
{
    void Start(){
        StartCoroutine(协程1());//开启协程

    }
    IEnumerator 协程1(){
       yield return 抢占支援(); //协程1
    }
    public int 抢占支援()
    {
        临界资源.ThreadMain();
         return 1;
    }
 
}

结果
即便开了100个脚本,全部打印的n=6。原因是在unity的主线程里只会有一个处于运行状态的协程,即便不同脚本开启多个协程或则同一脚本开启多给协程也是一个一个顺序执行。这是因为unity中有个协程调度器,里面有个协程列表协程调度器按照协程列表去执行,从而保证主线程里只能有一个处于运行状态的协程。
在这里插入图片描述

写在后面

哎,Unity工作好难找啊,55555

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/423540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

车载以太网基础篇之Eth Driver

车载以太网基础篇之Ethernet Driver前言 首先&#xff0c;请问大家几个小小问题&#xff0c;你清楚&#xff1a; 你知道Eth Driver模块的主要作用是什么吗&#xff1f;EthDriver与以太网控制器&#xff0c;以太网收发器&#xff0c;都有哪些关系呢&#xff1f;Eth Driver的常…

java虚拟机反射机制

&#xff08;1&#xff09;Java虚拟机反射机制的定义&#xff1f; Java反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功…

苍穹最终一致性使用

适用场景 最终一致模式可以保证跨数据库或跨节点更新时的数据一致。它会以1个更新操作为基准&#xff0c;注册多个其它更新操作&#xff0c;最终保证所有更新都成功&#xff0c;实现分布式事务的弱一致性。可以适用一个更新适用多个场景(跨云、跨库、跨系统) 工作原理 1、在第…

4月17日第壹简报,星期一,农历闰二月廿七

4月17日第壹简报&#xff0c;星期一&#xff0c;农历闰二月廿七坚持阅读&#xff0c;静待花开1. 风云三号G星顺利入轨&#xff01;我国成功发射首颗主动降水测量卫星&#xff08;能探测到毛毛雨般的降水&#xff09;。2. 四次出舱&#xff01;神舟十五号乘组刷新单个乘组出舱活…

Opencv c++ 图片截取不规则ROI区域

做目标检测的很多时候&#xff0c;需要我们选择一个相对固定的区域来识别目标。 思路很简单&#xff0c;适合相机固定&#xff0c;ROI变化不大的场景。 1、原图备份后先截取不规则的ROI区域&#xff0c;其他区域置为黑背景&#xff0c;检测识别效果 2、在原图上&#xff0c;将…

全新适配鸿蒙生态,Cocos引擎助力3D应用开发

一、适配HarmonyOS背景 HarmonyOS 3.1版本自发布以来&#xff0c;备受广大开发者的好评&#xff0c;同时也吸引了鸿蒙生态众多伙伴的青睐。 鸿蒙生态所强调的智慧全场景、多端联动与跨设备流转等能力&#xff0c;与Cocos所具有的跨平台、低功耗、高性能三大核心特点不谋而合。C…

银河麒麟服务器ky10 sp3 x86编译安装postgresql(包含uuid)

目录 下载 编译安装 目录说明 脚本文件说明 压缩包说明 脚本代码 下载 官网 PostgreSQL: The worlds most advanced open source database 源码下载地址 PostgreSQL: File Browser 有多个版本可以选择&#xff0c;我选择的是10.23 点击下载即可 我下载好之后把他上传到…

Qt网络编程 (udp广播和接收例)

大纲概述senderreceiver演示概述 使用两个项目 1 sender 用来广播"hello world";2 receiver 用来接收广播信息 sender 1 创建Qdialog类2 在sender.pro 中添加 QT network 一行代码3 在sender.h 中声明类 class QUdpSocket;在声明一个私有对象QUdpSocket *sender;…

ASEMI代理AD8226ARZ-R7亚德诺(ADI)车规级AD8226ARZ-R7

编辑&#xff1a;ll ASEMI代理AD8226ARZ-R7亚德诺&#xff08;ADI&#xff09;车规级AD8226ARZ-R7 型号&#xff1a;AD8226ARZ-R7 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOIC-8 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;8 类…

VS 创建ATL组件(C++)

目录1、新建ATL项目2、添加接口类、实现接口方法3、创建自己的方法。在头文件Add.h中添加声明4、在源文件Add.cpp中添加函数实现5、在MyComTest.idl中添加方法接口设置&#xff0c;如interface IAdd:IDispatch中所示。6、解决方案配置选择release, 平台选择x64,生成。MyComTest…

【数据结构与算法】判定给定的字符向量是否为回文算法

题目&#xff1a; Qestion: 试写一个算法判定给定的字符向量是否为回文。   回文解释: 回文是指正读反读均相同的字符序列&#xff0c;如“abba”和“abdba”均是回文&#xff0c;但“good”不是回文。 主要思路&#xff1a; 因为数据要求不是很严格并且是一个比较简单的…

软件测试—基础篇

软件测试—基础篇&#x1f50e;软件测试的生命周期&#x1f50e;如何描述一个BUG&#x1f50e;如何定义BUG 的级别&#x1f50e;BUG 的生命周期&#x1f50e;测试的执行与管理测试的执行与管理如何发现更多的BUG&#x1f50e;产生争执怎么办&#x1f50e;结尾&#x1f50e;软件…

基于PaddlePaddle的词向量实战 | 深度学习基础任务教程系列

词向量是自然语言处理中常见的一个操作&#xff0c;是搜索引擎、广告系统、推荐系统等互联网服务背后常见的基础技术。 在这些互联网服务里&#xff0c;我们经常要比较两个词或者两段文本之间的相关性。为了做这样的比较&#xff0c;我们往往把词表示成计算机适合处理的方式。最…

CRM管理系统有哪些优势?在企业中有什么作用?

CRM管理系统有哪些优势&#xff1f;在企业中有什么作用&#xff1f; 其实&#xff0c;公司上crm管理系统不仅不会增加员工负担&#xff0c;还能提升员工满意度、赋能员工。当然&#xff0c;前提是一款优质的CRM管理系统。 那么&#xff0c;一款优质的CRM管理系统一般具备哪些…

45.Promise,async,await

目录 1 Promise 1.1 状态 1.2 then 1.3 任务的优先级 1.4 连续then 1.4.1 then()的返回值也是一个Promise对象 1.4.2 默认情况下&#xff0c;在第一个then之后&#xff0c;会执行第二个then的成功函数 1.4.3 then的value可以用返回值传递 1.4.4 通过then的返…

为什么老年人会经常性出现吃饭呛咳的情况 什么因素导致的

其实很多老年人在吃饭或是喝水的时候&#xff0c;都存在吞咽困难的问题&#xff0c;呛咳或者是忘了如何下咽。其实在老年人群体当中&#xff0c;这也是一种较为常见的现象&#xff0c;但是很多人都把这种现象当回事。 对于呛咳的现象是很好判断的&#xff0c;在家里老人喝水或是…

专注区块链底层技术突破,“复杂美”用技术开源推动产业未来

杭州复杂美科技有限公司&#xff08;以下简称&#xff1a;复杂美&#xff09;成立于2008年&#xff0c;是一家致力于高性能撮合技术研发的区块链底层技术提供商&#xff0c;专注于区块链撮合系统、区块链清算系统的应用与推广。 公司于2013年启动区块链、智能合约的研发应用&am…

PyTorch 深度学习实战 | 基于 ResNet 的花卉图片分类

“工欲善其事&#xff0c;必先利其器”。如果直接使用 Python 完成模型的构建、导出等工作&#xff0c;势必会耗费相当多的时间&#xff0c;而且大部分工作都是深度学习中共同拥有的部分&#xff0c;即重复工作。所以本案例为了快速实现效果&#xff0c;就直接使用将这些共有部…

36岁大龄程序员被裁,找了2个月工作,年包从100万降到50万,要不要接?

为了找到工作&#xff0c;你愿意接受降薪多少&#xff1f;一位36岁的杭州程序员问&#xff1a;36岁被裁&#xff0c;找了2个月工作&#xff0c;年包从100万降到50万&#xff0c;真心纠结&#xff0c;要不要接&#xff1f;网友们分成了旗帜鲜明的两派&#xff0c;一派人认为不要…

【数学模型】欧拉公式和证明

一、说明 在图型学中&#xff0c;欧拉公式很有用处&#xff0c;比如皮克定律也可以用欧拉公式证明。本篇介绍欧拉公式的定义和三个证明过程。 二、欧拉公式 在任何一个规则球面地图上&#xff0c;用 R记区域个 数 &#xff0c;V记顶点个数 &#xff0c;E记边界个数 &#xff0c…