Unity中的多线程

366 阅读6分钟

什么是线程?

线程是操作系统能够进行运算调度的最小单位,包含在进程中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流。
一个进程可以包含多个线程,每条线程并行执行不同的任务。

为什么要使用线程

线程是并行执行的。Unity中有协程可以协助主线程运算,但是协程计算量过大,会影响主线程执行。
例子:协程等待两秒,Update中主线程要进行视角转动,发现转不动,这是协程阻塞了主线程。

Unity可以使用多线程,但是为什么要避免

Unity本身的UnityEngine所使用的API不能被多线程调用,所以Unity不能使用多线程。
但是C#中可以使用多线程,Unity使用C#编辑,所以Unity是可以通过C#调用多线程。

Unity多线程注意事项

1.变量都是共享的,都指向相同的内存地址
2.UnityEngine的API不能在分线程运行
3.UnityEngine定义的基本结构(intfloatstruct)可以在分线程计算,如Vector3(struct)可以,但是Texture2D(class)不可以
4.UnityEngine定义的基本类型可以在分线程运行

线程的生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。

新建:就是刚使用new方法,new出来的线程;

就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、
wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用
notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

线程基础方法使用

new Thread():创建一个线程

start():开启创建的线程

join():当前线程等待另一个线程结束后,在执行

Sleep();等待N毫秒后继续执行

Suspend():该方法并不终止未完成的线程,它仅仅挂起当前线程,以后还可恢复;

Resume():恢复被Suspend()方法挂起的线程的执行。

Abort():结束线程

1.线程调用有参无参的方法函数

using System.Threading;
using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    Thread threadB;
    // Start is called before the first frame update
    void Start()
    {
        threadA = new Thread(AA);
        threadB = new Thread(new ParameterizedThreadStart(BB));
        threadA.Start();
        threadB.Start("B线程");
    }
    //无参
    void AA()
    {
        for (int i = 0; i < 1000; i++)
        {
            Debug.Log("A线程" + i);
        }
    }

    //有参,且有参函数类型必须是object类型
    void BB(object a)
    {
        for (int i = 0; i < 1000; i++)
        {
            Debug.Log(a.ToString() + i);
        }
    }
}

2.Join方法使用

注意一点 Join这个方法会占用很多CPU资源,要小心利用,Join在线程执行完之前分配大量的时间片给该线程,直到线程结束后。

using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    Thread threadB;
    // Start is called before the first frame update
    void Start()
    {
        threadA = new Thread(AA);
        threadB = new Thread(new ParameterizedThreadStart(BB));
        threadA.Start();
        threadA.Join();//在CPU加入threadA的结束判断当threadA线程结束后 在执行后面的线程方法
        threadB.Start("B线程");
    }
    //无参
    void AA()
    {
        for (int i = 0; i < 1000; i++)
        {
            Debug.Log("A线程" + i);
        }
    }

    //有参,且有参函数类型必须是object类型
    void BB(object a)
    {
        for (int i = 0; i < 1000; i++)
        {
            Debug.Log(a.ToString() + i);
        }
    }
}

3.Sleep()等待睡眠结束

using System.Threading;
using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    Thread thread_AA;
    Thread thread_BB;

    private void Start()
    {
        thread_AA = new Thread(AA);
        thread_BB = new Thread(new ParameterizedThreadStart(BB));
        thread_AA.Start();
        thread_BB.Start("hhhh");
    }

    void AA()
    {
        Debug.Log("线程AA开始执行");
        Thread.Sleep(3000);  //对应的线程睡眠三秒钟
        Debug.Log("线程AA等待了3秒钟");
    }

    void BB(object ooo)
    {
        for (int i = 0; i < 3; i++)
        {
            Thread.Sleep(1000);
            Debug.Log(ooo.ToString() + i);
        }
    }
}

4.Abort杀死线程(停止线程)

using System.Threading;
using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    Thread thread_AA;

    private void Start()
    {
        thread_AA = new Thread(AA);
        thread_AA.Start();
    }

    void AA()
    {
        Debug.Log("线程AA开始执行");
        while (true)
        {
            Thread.Sleep(1000);
            Debug.Log("hhhh");
            
        }     
    }
    private void OnApplicationQuit()
    {
        //退出程序时需要杀死线程,否则会出现错误,需要重启Unity
        thread_AA.Abort();
    }
}

5.做一个按钮控制开关线程

using System.Threading;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class ThreadTest : MonoBehaviour
{
    public Button button;
    bool isStart = false;
    Thread threadA;
    private void Start()
    {
        threadA = new Thread(AA);
        threadA.Start();
        button.onClick.AddListener(delegate
        {

           
            button.transform.GetComponentInChildren<TMP_Text>().text = isStart ? "Open" : "Close";
            isStart = !isStart;

        }
        );
    }

    void AA()
    {
        //死循环 每过1秒执行一次
        while (true)
        {
            if (isStart)
            {
                Debug.Log("A线程执行");
                Thread.Sleep(1000); //1000毫秒 等待1秒钟
            }
        }
    }
}

6.使用协程控制线程一秒打印一次

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.Threading;
using System.IO;
 
public class ThreadTest : MonoBehaviour
{
    Thread threadA;
    void Start()
    {
        threadA = new Thread(AA);
        threadA.Start();
        StartCoroutine(Test());
    }
 
    IEnumerator Test()
    {
        while (true)
        {
            isEnd = true;
            yield return new WaitForSeconds(1f);
        }
    }
 
    bool isEnd = false;
    //无参
    void AA()
    {
        while (true)
        {
            if (isEnd)
            {
                isEnd = false;
                Debug.Log("A线程执行");
            }
        }
 
    }
 
    void OnApplicationQuit()
    {
        //结束线程必须关闭 否则下次开启会出现错误 (如果出现的话 只能重启unity了)
        threadA.Abort();
    }
 
}

7线程池的使用

六、线程池的使用
 .NET FrameworkThreadPool类提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。那么什么是线程池?线程池其实就是一个存放线程对象的“池子(pool)”,他提供了一些基本方法,如:设置pool中最小/最大线程数量、把要执行的方法排入队列等等。ThreadPool是一个静态类,因此可以直接使用,不用创建对象。

有点类似Unity中的对象池,当要使用线程的时候我们线程池查找是否有空闲的线程,有就使用,没有就创建生成。

微软官网说法如下:

许多应用程序创建大量处于睡眠状态,等待事件发生的线程。还有许多线程可能会进入休眠状态,这些线程只是为了定期唤醒以轮询更改或更新的状态信息。 线程池,使您可以通过由系统管理的工作线程池来更有效地使用线程。 

所以线程池一般是在需要大量线程,并且线程的数据处理都很小的情况下使用

使用方法很简单:

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.Threading;
using System.IO;
 
public class ThreadTest : MonoBehaviour
{
    void Start()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(AA), null);
    }
 
    //无参
    void AA(object a)
    {
        Debug.Log("A线程执行");
    }
 
}
这里要注意几点:

1.线程池的方法必须是有参方法,而且传参不能超过22.如果方法使用死循环,Unity结束运行后,还是会执行线程方法。