首页 > 编程语言 >Unity网络编程(1)线程

Unity网络编程(1)线程

时间:2024-09-13 20:53:10浏览次数:3  
标签:lock 编程 Unity 线程 deadTime RiseThread using public

引入:网络编程基础认识

1.了解操作系统的分时操作:

操作系统将时间划分为很多个片段,尽可能均匀地分配给正在执行的线程

获得时间片的进程得以运行,其他则在等待

CPU在这些进程上来回切换,频密,让人感觉多个进程在同时执行

2.概念认识:

(1)进程是程序的边界,程序与程序间以进程为隔离    靠线程执行程序,

   线程是进程内部的组成单位,指向方法,执行完毕释放线程

(2)进程是资源分配的单位,

   线程是执行的单位,是进程中的一个执行单元,是CPU分配时间片的单位

(3)线程的本地存储器:TLS

线程的本地存储器(Thread Local Storage, TLS)是一种用于在多线程程序中为每个线程提供独立存储区域的机制。它允许每个线程有自己独立的变量副本,这些变量在不同线程之间不会互相干扰,从而避免了数据竞争和同步问题。

(4)Socket编程:使多个进程间能够进行通信,网络编程中,Socket 是一种用于网络通信的基本概念和接口。它是一种抽象层,提供了不同网络协议(如 TCP/IP 和 UDP)的通信机制,允许应用程序在网络上进行数据交换。Socket 允许程序与其他网络节点(例如计算机、服务器等)进行连接和数据传输。

3.可能疑问

1.辅助线程和日常使用的异步async协程 Coroutine的区别:

这里要明确的是异步和携程 主要依赖于主线程,并不会自动创建新的线程。


一.线程

1.对线程的印象

Unity默认只有一条主线程,在主线程内运行Unity生命周期函数,但是Unity是支持多线程的。

日常生活中我们可能会遇到这样一种情况:

在高中的时候,我们一般都会遇到在食堂的一个窗口前排队买饭的情景,现假设只有一个窗口可以供同学们打饭,每位同学依次排队打一 ~ 五单位的饭,但是,如果出现了一位同学,假如他的饭量有亿点大,要打100个单位的饭,那他后面的同学岂不是都饥肠辘辘了?这时,我们可以多开几个窗口,让一个窗口专门去处理这位大胃王的饭,这样一来全体同学的打饭效率就得到了提升

线程的作用可以类比上面的例子。

2.为什么使用线程

当主线程内运行计算量大的耗时计算时,移至线程计算

使用多线程减少程序的运行时间

3.线程的优缺点

优点:

并行执行,合理利用CPU资源

相同程序的线程共享堆内存

缺点:                                             |       解决方案

频繁创建、销毁线程增加性能开销  |(使用线程池)

访问共享资源可能造成冲突             |(使用lock来同步线程)

辅助线程不能访问Unity API            |(“外包”给委托)

4.线程的创建

说了这么多,我们直接了解如何创建线程吧 @\/@

我们先定义到Thread内部去看看Thread内置的方法

可以看到接收的是一个委托类型的参数,且该委托的参数为无参或object类型

我们可以利用一个无参函数,也可以通过匿名函数来创建线程

实验一下,在这里我写了三种形式,代码里的System.Sleep()表示令调用其的线程休眠指定时间

在编辑器内挂载该脚本并运行得到结果如下:

5.信号灯(ManualResetEvent :手动重置事件)

ManualResetEvent 常被称为信号灯,用来控制目标线程的暂停与运行,

首先创建一个信号灯,参数设置为false,此时所有signal控制线程初始都会暂停

//创建信号灯
ManualResetEvent signal=new ManualResetEvent(false); 
//false:初始处于关闭状态 ;true:初始处于通行状态

这里我使用Unity内置的OnGUI()函数来进行实验,Set表示通行  ReSet表示禁止通行

编辑器内运行结果:一开始所有线程都是处于禁止通行状态,按下继续Button,线程开始运行,

注意:此时如果再次按下暂停不会有效果,原因是 ManualResetEvent的自身特性 ,在调用一次Set()后将允许恢复所有被阻塞线程。需手动在调用WaitOne()之后调用Reset()重置信号量状态为非终止,然后再次调用WaitOne()的时候才能继续阻塞线程,反之则不阻塞。

6.线程池(线程缺点解决方案):

前面提到线程的缺点之一:频繁创建、销毁线程增加性能开销 

我们可以采用线程池技术来缓解这一限制:

频繁创建和销毁线程时使用,能有效减少以及系统的开销

ThreadPool.QueueUserWorkItem(obj => 
        {
            ThreadpollTest();
        });

7.线程同步(线程缺点解决方案):

继续说线程的缺点之二:访问共享资源可能造成冲突 

我们采用lock来处理这样的问题,让多个线程在调用共享资源的时候也按照一定的先后顺序来执行。

关于lock:

(1)什么时候需要考虑用锁:

多线程  多个线程同时访问共享资源时   需要考虑用锁

(2)lock有什么用:

 lock 关键字用于在多线程环境下同步对某些资源的访问,以防止多个线程同时修改同一数据,       从而避免竞争条件。它确保在任何时刻,只有一个线程能够执行被锁住的代码块。

   在 C# 中,lock 关键字依赖于对象的互斥量(mutex)来实现线程同步。这种机制确保在任何时     刻,只有一个线程可以进入被 lock 保护的代码块,从而避免了多个线程对共享资源的并发访         问。

(3)lock实现机制:

  • 对象互斥量lock 关键字实际上是对一个对象进行加锁。这个对象通常是一个object类型的实例。在 lock 关键字内部,使用的是系统级别的互斥量来实现这一机制。这个互斥量确保同一时间只有一个线程可以访问被锁定的代码块。

  • Monitor 类:在后台,lock 关键字利用了 System.Threading.Monitor 类来实现锁定操作。Monitor 提供了一些方法,比如 EnterExit,用于获取和释放锁。lock 关键字实际上是对这些方法的语法糖,使得代码更简洁和易读。

(4)lock工作原理:

  1. 尝试获取锁:当一个线程执行到 lock 语句时,它会尝试获取指定对象的锁。
  2. 进入临界区:如果锁当前没有被其他线程持有,那么线程会成功获取锁并进入被保护的代码块。
  3. 释放锁:当线程执行完锁保护的代码块后,它会释放锁,允许其他线程获取该锁。

(5)lock的使用:在需要锁住的共享资源(经常是一份公共可修改的变量数据)逻辑实现外用

 lock(locker)                          //locker是引用类型的参数

  {         }     结构来锁住这段逻辑,实现线程同步和线程安全      

8.前后台程序

通过Thread创建的线程默认是前台

前台线程:程序必须等待所有前台线程结束后才能退出

通过ThreadPool创建的线程默认是后台

后台线程:程序不考虑后台线程,当程序结束时后台线程随之结束

Unity程序结束时,前台程序随即关闭,总结:在unity里其实不必过度区分前后台线程,有基础的认识即可,通过Thread内部成员字段 isBackground来描述

9.线程状态

我们先了解四种常用的线程状态:

(1)Unstarted:未启动状态,创建线程对象时默认的状态

(2)Running:运行状态,执行绑定的方法(注意:Running状态的线程其实还是走走停停的,     等待CPU分配时间片)

(3)WaitSleepJoin:等待睡眠阻塞状态,暂时停止执行,将资源交给其他线程使用

(4)Stoppd:终止状态,线程销毁

(5)AbortRequested:在 Unity 中,AbortRequestUnityEngine.AsyncOperation 类中的一个线程状态标志。它表示操作请求被中止,即异步操作正在被取消或已被标记为中止。这通常在加载场景或资源时,操作被取消或需要中止时使用。

应用场景:

创建线程     线程为Unstarted状态

使用信号灯,System.Sleep() 等手段使阻止线程

线程执行结束,线程进入Stopped状态,随即释放

常在Unity生命周期函数 OnApplicationQuit()【此函数将在程序即将结束时调用】 内执行myThread.Abort(); 主要作用是请求终止一个正在执行的线程。

10.实现在线程内调用Unity内置API(线程缺点解决方案)

最后说一下unity里线程的缺点之三:辅助线程不能访问Unity API 

 我们进行一个例子的引入:我想在一个辅助线程里实现 Canvas上的一个Text组件打印数字倒计时的功能,

代码1版

using System.Threading;
using TMPro;
using UnityEngine;
using UnityEngine.Events;

public class ThreadTest:MonoBehaviour
{

Thread myThread1;
public TMP_Text myText;
public float deadTime = 160;

 private void Start()
 {
    myThread1=new Thread(()=>
             {
               while (deadTime > 0)
               {
                 deadTime--;
                 myText.text = "Time:" + (int)(deadTime / 60) + ":" + (int)(deadTime % 60);
                 Thread.Sleep(1000);
                }
             });
    myThread1.Start();
 }

}

也许你已经经过尝试,启动!结果console窗口里却出现了一位不速之客:

报错显示:get_time只能在主线程中调用

这下怎么办呢,既然线程内部无法调用Unity的内置API,我们就避开线程内部调用,将函数内的逻辑交给一个委托(UnityAction来处理)

代码2版

using System.Threading;
using TMPro;
using UnityEngine;
using UnityEngine.Events;

public class ThreadTest : MonoBehaviour
{
    Thread myThread1;
    public TMP_Text myText;
    public float deadTime = 160;
    UnityAction myAction;
    private void Start()
    {
        myThread1 = new Thread(() =>
        {
            while (deadTime > 0)
            {
                deadTime--;
                myAction = () => { myText.text = "Time:"   + 
                                   (int)(deadTime / 60)    + ":" + 
                                   (int)(deadTime % 60); };

                Thread.Sleep(1000);
            }
        });
        myThread1.Start();
    }

    private void Update()
    {
        myAction?.Invoke();
        myAction = null;
    }
}

编辑器内运行,发现功能已经可以正常实现 

这就相当于将线程内要执行的逻辑放入一个委托中,Updata函数内时刻检测此委托内是否含有方法逻辑 ,有就执行

我们便可抽象出一个线程委托Manager来专门管理线程 并实现 线程内调用Uinty内置API的功能

见下方图所示

二.综合应用实现(线程管理及线程调用Unity内置APIManager)

由于是在学习过程中自行尝试实现,可能会有不足之处及改进空间 %(^-*)% 希望各位大佬不吝赐教

1.实现目标

  在任意脚本中实现   自行控制开启时间(延迟)辅助线程的运行 并且 能正常执行Unity内置方法

(类似用辅助线程实现出协程的效果,但线程和协程这两者肯定是风马牛不相及的,达咩搞混~)

不妨就以上面举出的Text例子来尝试一下

2.实现思路

3.代码设计

(1)ThreadDelayAction:定义一种数据结构,包含委托和延迟时间,下称 委托复合单元

(2)RiseThread:定义一种数据结构,包含线程和其他高维变量,     下称 线程复合单元

(3)ThreadActionManager所需功能:

  1.包含委托复合单元的列表

  2.开启目标线程复合单元内的线程

  3.Update内时刻遍历列表内是否有延迟时间满足执行的委托逻辑

(4)ThreadRiseManager所需功能:

  1.包含线程复合单元的字典<string riseThreadName,RiseThread  riseThread>

  2.向线程复合单元内的线程植入委托

  3.满足条件能够开启目标线程复合单元内的线程

4.实现源码

ThreadRiseManager类

using System.Collections.Generic;
using System.Threading;
using UnityEngine.Events;
public class RiseThread
{
    public Thread thread;
    //string threadName;
    //public UnityAction threadAction;
    /// <summary>
    /// 未来可能加入多维信息
    /// </summary>
    /// <param name="_thread"></param>
    public RiseThread(Thread _thread)
    {
        thread = _thread;
    }
}

public class ThreadRiseManager : MonoSingleton<ThreadRiseManager>
{
    Dictionary<string, RiseThread> threadDic = new Dictionary<string, RiseThread>();
    RiseThread threadRise1;
    RiseThread threadRise2;
    RiseThread threadRise3;
    //RiseThread threadRise4;
    //RiseThread threadRise5;
    //RiseThread threadRise6;
    List<RiseThread> threadRiseList = new List<RiseThread>();
    Thread thread1, thread2, thread3;// thread4,thread5,thread6;
    void Start()
    {
        threadRise1 = new RiseThread(thread1);
        threadRise2 = new RiseThread(thread2);
        threadRise3 = new RiseThread(thread3);
        //threadRise4=new RiseThread(thread4);
        //threadRise5=new RiseThread(thread5);
        //threadRise6=new RiseThread(thread6);
        threadRiseList.Add(threadRise1);
        threadRiseList.Add(threadRise2);
        threadRiseList.Add(threadRise3);
        //threadRiseList.Add(threadRise4);
        //threadRiseList.Add(threadRise5);
        //threadRiseList.Add(threadRise6);
        AddDicListener();
    }
    void AddDicListener()
    {
        for (int i = 1; i <= threadRiseList.Count; i++)
        {
            AddSingleDicListener("threadRise" + i, threadRiseList[i - 1]);
        }
    }
    void AddSingleDicListener(string riseThreadName, RiseThread riseThread)
    {
        if (!threadDic.ContainsKey(riseThreadName))
        {
            threadDic.Add(riseThreadName, riseThread);
        }
    }

    //外部直接传入函数方法,传入后将此函数传入对应threadRise的委托函数内,
    //先在目标线程内添加待执行函数,等待时机开启线程
    public void SetMyThread(string threadRiseName, UnityAction action)
    {
        if (threadDic.ContainsKey(threadRiseName))
        {
            print("目标threadRise已添加监听委托");
            threadDic[threadRiseName].thread = new Thread(() => { action(); });
        }
        //如果没有发现目threadRise,则返回
        else
        {
            print("错误:未发现目标threadRise!");
        }
    }
    /// <summary>
    /// 开启目标线程
    /// </summary>
    /// <param name="threadRiseName"></param>
    /// <param name="excute"></param>
    public void StartMyThread(string threadRiseName, bool excute)
    {
        if (threadDic.ContainsKey(threadRiseName))
        {
            print("开始执行线程:" + threadRiseName);
            if (!threadDic[threadRiseName].thread.IsAlive)
            {
                threadDic[threadRiseName].thread.Start();
            }
        }
    }

    /// <summary>
    /// 程序结束,自动终结所有辅助线程
    /// </summary>
    private void OnApplicationQuit()
    {
        foreach (var item in threadDic)
        {
            if (item.Value.thread != null)
                item.Value.thread.Abort();
        }
    }
}

ThreadActionManager类

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class ThreadDelayAction
{
    public UnityAction threadAction;
    public float timeDelay;
    public ThreadDelayAction(UnityAction _threadAction, float _timeDelay)
    {
        threadAction = _threadAction;
        timeDelay = _timeDelay;
    }
}
public class ThreadActionManager : MonoSingleton<ThreadActionManager>
{
    private List<ThreadDelayAction> threadDelayActionLists=new List<ThreadDelayAction>();
    object locker = new object();
    protected override void Awake()
    {
        base.Awake();
    }
    private void Update()
    {
        for (int i = threadDelayActionLists.Count - 1; i >= 0; i--)
        {
            if (threadDelayActionLists[i].timeDelay <= Time.time)
            {
                threadDelayActionLists[i].threadAction?.Invoke();
                threadDelayActionLists.Remove(threadDelayActionLists[i]);
            }
        }
    }
    /// <summary>
    /// 实际执行开启的线程
    /// </summary>
    /// <param name="action"></param>
    /// <param name="delay"></param>
    public void AddThreadStartListener(UnityAction action, float delay=0)
    {
        //锁一下list,线程同步,更安全
        lock (threadDelayActionLists)
        {
            ThreadDelayAction newThreadDelayAction = new ThreadDelayAction(action, delay);

            threadDelayActionLists.Add(newThreadDelayAction);
            print("新添加辅助线程委托监听");
        }  
    }
}

ThreadHelpTest类(测试脚本)

using System.Threading;
using TMPro;
using UnityEngine;

public class ThreadHelpTest : MonoBehaviour
{
    public TMP_Text myText;
    public float deadTime = 160;

    private void Start()
    {
        ThreadRiseManager.Instance.SetMyThread("threadRise1", ThreadFun1);
    }

    private void Update()
    {
        //自行控制线程的开启时间
        if (Input.GetKey(KeyCode.Space))
        {
            print("按下了Space,开启待执行线程");
            ThreadRiseManager.Instance.StartMyThread("threadRise1", true);
        }
    }
    void ThreadFun1()
    {
        while (deadTime > 0)
        {
            deadTime--;
            ThreadActionManager.Instance.AddThreadStartListener(() =>
            {
                myText.text = "Time:" + (int)(deadTime / 60) + ":" + (int)(deadTime % 60);
            }, 5);
            Thread.Sleep(1000);
        }
    }
}

本篇完

标签:lock,编程,Unity,线程,deadTime,RiseThread,using,public
From: https://blog.csdn.net/xxxxx666666/article/details/142215954

相关文章

  • 谈谈flutter的线程
    本文同步发布于公众号:移动开发那些事谈谈flutter的线程刚接触flutter的同学肯定会对fluter所谓的单线程架构很蒙逼,因为这与我们学开发时,各种语言里的多线程的介绍有点出入,而且手机的CPU现在基本都是多核的,操作系统不可能同一时间只在处理一件事件的,那么flutter究竟是怎样实现其......
  • 【HBuilderX-从下载到项目创建】编程初学者适用的HBuilderX开发环境(超详细的)下载安装
    简介:HBuilderX是一款由DCloud公司开发的集成开发环境(IDE),专为前端开发设计,同时也支持多平台应用开发。它支持HTML、CSS、JavaScript、Vue、React、Uni-app等多种编程语言和框架,具备代码编辑、调试、测试等功能,并且提供了丰富的插件生态系统以扩展其功能。“......
  • 联网对话功能上线,CodeGeeX智能编程助手再度升级!
    CodeGeeX上线联网获取信息的新功能!进一步提升在编程场景中的实用性和智能水平。值得一提的是,联网对话这一创新功能,目前在编程工具的同类型产品中,是独一无二的。下面我们就来一起看看这一创新为开发者带来的价值,以及如何运用它来提升编程效率和问题解决的能力。首先,打开联网获取信息......
  • 全网最适合入门的面向对象编程教程:47 Python函数方法与接口-回调函数Callback
    全网最适合入门的面向对象编程教程:47Python函数方法与接口-回调函数Callback摘要:回调函数是编程中一种非常常见的模式,用于将函数作为参数传递给其他函数或方法。这种模式在Python中广泛应用于事件处理、异步编程、函数式编程等场景。原文链接:FreakStudio的博客往......
  • 如何高效记录并整理编程学习笔记?
    在编程学习的海洋中,高效的笔记记录和整理方法就像一张珍贵的航海图,能够帮助我们在浩瀚的知识中找到方向。如何建立一个既能快速记录又易于回顾的笔记系统?如何在繁忙的学习中保持笔记的条理性?让我们一起探讨如何打造属于自己的编程学习“知识宝库”! 方向一:笔记工具选择以下是几款......
  • 《C++编程规范》六、构造、析构与复制
    目录第47条以同样的顺序定义和初始化成员变量使用这些函数之所以需要小心,其中一个原因是几乎一半的情况下编译器都会为我们生成代码。另一个原因是,C++默认时总是将类当作类似于值的类型,但是实际上并非所有的类型都类似于值(见第32条)。知道何时应该显式地编写(或者禁止)这些特殊......
  • 【编程小白必看】使用Selenium进行网页自动化操作操作秘籍一文全掌握
    【编程小白必看】使用Selenium进行网页自动化操作操作秘籍......
  • 深入理解Redis线程模型
    Reids6.0之前版本的线程模型在讨论最新版本的Redis的线程模型之前呢,我们先来聊聊原来的Redis的线程模型:有人说,在6.0之前呢,Redis是单线程的,这么说其实也不太准确,为什么呢?因为Redis在4.0之后,就引入了多线程,比如说除了处理用户命令的主线程之外,还会起异步的线程去做一些资源释......
  • 为什么那么多开源软件都用netty来做网络通信编程框架?
     1、用netty来做网络通信编程框架而不是我们自己去基于JDKNIO来编程的好处有如下这些:(1)、netty支持常见的应用层协议(如:HTTP、FTP、DNS等),还可以支持自定义协议;(2)、netty可以自动解决网络编程当中的粘包与半包问题;(3)、netty还可以支持流量整形;(4)、netty对于网络通信当中......
  • 编程小白如何成为大神?大学新生的最佳入门攻略
    方向一:编程语言选择选择原则:兴趣驱动:选择自己感兴趣的编程语言开始学习,兴趣是最好的老师。实用性:考虑语言的实用性,如Python、Java、C++等都是广泛使用的编程语言。职业目标:根据未来职业规划选择语言,例如想做数据分析可以学Python,想做安卓开发可以学Java。推荐语言:Python:语法简洁,易......