协程与多线程
一、协程
协程其实就是一个IEnumerator(迭代器),
IEnumerator 接口有两个方法 Current 和 MoveNext()
Unity在每帧做的工作就是:调用 协程(迭代器)MoveNext() 方法,如果返回 true ,就从当前位置继续往下执行。
协程是什么?
协同程序,即在主程序运行时同时开启另一段逻辑处理,来协同当前程序的执行。换句话说,开启协同程序就是开启一个线程。
协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。通俗点说:程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
协程的运行
协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。
协同程序可以和主程序并行运行,和多线程有点类似。
Paste_Image.png协程的作用
1、延时(等待)一段时间执行代码;
2、等某个操作完成之后再执行后面的代码。
总结起来就是一句话:控制代码在特定的时机执行。
WaitForSeconds()受Time.timeScale影响,当Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足。
练习测试
void Start () {
print("开始下载" + "---" + Time.time);
StartCoroutine(waitOne());
print("正在下载----50%" + "---" + Time.time);
}
IEnumerator waitOne()
{
Debug.Log("加入下载列表" + "---" + Time.time);
yield return StartCoroutine(waitTwo());
print("下载完成" + "---" + Time.time);
}
IEnumerator waitTwo()
{
print("正在下载----1%" + "---" + Time.time);
yield return new WaitForSeconds(3);
print("正在下载----70%" + "---" + Time.time);
}
打印结果(执行顺序):
Paste_Image.png
禁用该脚本协程是否还会执行?
Paste_Image.png
二、WaitUntil和WaitWhile的秘密
开始学习WaitUntil:
θ 根据定义,它挂起语句,直到指定的条件返回true。
θ 换句话说,当我们指定的条件是false时,它不会继续执行语句。Unity将会等待条件返回true。
测试
public int counter;
void Start ()
{
counter = 0;
StartCoroutine(FuelNotification());
}
IEnumerator FuelNotification()
{
Debug.Log("开始通关" + "---" + Time.time);
yield return new WaitUntil(IsTankEmpty);
Debug.Log("通关了!" + "---" + Time.time);
}
void Update()
{
if (counter<21)
{
Debug.Log("通关数量:"+counter+"---"+Time.time);
counter++;
}
}
public bool IsTankEmpty()
{
if (counter<21)
{
return false;
}
else
{
return true;
}
}
打印结果:
Paste_Image.png
下面我们再来看看怎么使用Lambda表达式。Lambda表达式是一个非常便利的工具,试一试吧。
public int counter;
void Start()
{
counter = 20;
StartCoroutine(FuelNotification());
}
void Update()
{
if (counter > 0)
{
Debug.Log("Fuel Level:" + counter + "---" + Time.time);
counter--;
}
}
IEnumerator FuelNotification()
{
Debug.Log("Waiting for tank to get empty" + "---" + Time.time);
yield return new WaitUntil(() => counter <= 0);
Debug.Log("Tank Empty!" + "---" + Time.time);
}
打印结果 :
现在来学习WaitWhile:WaitWhile是WaitUntil的对立面。它抑制表达式,当条件为真。不像WaitUntil,它将会等待条件变为false,才能指向后面的代码块。和前面同样的例子,我们使用WaitWhile:我们只需要改变Lambda表达式,让其等于False,其打印结果是一样的。
public int counter;
void Start()
{
counter = 20;
StartCoroutine(FuelNotification());
}
void Update()
{
if (counter > 0)
{
Debug.Log("Fuel Level:" + counter + "---" + Time.time);
counter--;
}
}
IEnumerator FuelNotification()
{
Debug.Log("Waiting for tank to get empty" + "---" + Time.time);
yield return new WaitUntil(() => counter > 0);
Debug.Log("Tank Empty!" + "---" + Time.time);
}
image.png
三、线程与协程的区别
线程
线程之间共享变量,解决了通讯麻烦的问题,但是对于变量的访问需要锁,线程的调度主要也是有操作系统完成,一个进程可以拥有多个线程,但是其中每个线程会共享父进程向操作系统申请资源,这个包括虚拟内存、文件等,由于是共享资源,所以创建线程所需要的系统资源占用比进程小很多,相应的可创建的线程数量也变得相对多很多。线程时间的通讯除了可以使用进程之间通讯的方式以外还可以通过共享内存的方式进行通信,所以这个速度比通过内核要快很多。另外在调度方面也是由于内存是共享的,所以上下文切换的时候需要保存的东西就像对少一些,这样一来上下文的切换也变得高效。
协程
协程的调度完全由用户控制,一个线程可以有多个协程,用户创建了几个线程,然后每个线程都是循环按照指定的任务清单顺序完成不同的任务,当任务被堵塞的时候执行下一个任务,当恢复的时候再回来执行这个任务,任务之间的切换只需要保存每个任务的上下文内容,就像直接操作栈一样的,这样就完全没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快;另外协程还需要保证是非堵塞的且没有相互依赖,协程基本上不能同步通讯,多采用一步的消息通讯,效率比较高。