Answers:
虽然协程乍一看似乎像线程一样工作,但实际上它们并未使用任何多线程。它们按顺序执行,直到它们执行yield
。引擎将检查所有产生的协程作为其自己的主循环的一部分(此时确切地取决于的类型yield
,请检查此图以获取更多信息),将它们一个接一个地继续直到下一个yield
,然后再继续进行主循环。
该技术的优势在于,您可以使用协程,而不会因真正的多线程问题而引起头痛。您不会因上下文切换而导致任何死锁,争用条件或性能问题,您将能够正确调试,并且无需使用线程安全的数据容器。这是因为执行协程时,Unity引擎处于受控状态。使用大多数Unity功能是安全的。
另一方面,对于线程,您绝对不了解Unity主循环当前处于什么状态(实际上可能根本不再运行)。因此,您的线程可能由于一次不应该做的事情而造成很多麻烦。不要触摸子线程中的任何本机Unity功能。如果您需要在子线程和主线程之间进行通信,请在通常的Unity事件函数中使该线程写入某个线程安全(!)容器对象,并让MonoBehaviour读取该信息。
不执行“真正的”多线程的缺点是您不能使用协程在多个CPU内核上并行化CPU密集型计算。但是,您可以使用它们将计算划分为多个更新。因此,您不必在几秒钟内获得较低的平均帧速率,而无需将游戏冻结一秒钟。但是在这种情况下,yield
每当您要允许Unity运行更新时,您都要对协程负责。
结论:
协程在计算机科学中被我们称为“协作多任务”。它们是多个不同执行流相互协作交错的一种方式。在协作式多任务处理中,一个执行流对CPU拥有唯一的争议,直到CPU达到yield
。此时,Unity(或您使用的任何框架)可以选择切换到其他执行流。然后,它也将获得唯一的CPU所有权,直到获得yield
s 为止。
线程就是我们所说的“抢先式多任务处理”。当您使用线程时,框架保留随时停止您的线程中间思想并切换到另一个线程的权利。不管你在哪里。在某些情况下,甚至可以通过将变量写入内存来部分停止!
每个都有优点和缺点。协程的缺点可能最容易理解。首先,协程全部在单个内核上执行。如果您具有四核CPU,则协程将仅使用四个核之一。这简化了事情,但在某些情况下可能是性能问题。第二个缺点是,您必须意识到任何协程都可以通过拒绝来停止整个程序yield
。多年前,这是Mac OS9上的一个问题。OS9仅在整个计算机上支持协作式多任务处理。如果您的程序之一挂起,则可能会导致计算机停止运行,以至于OS甚至无法呈现错误消息的文本以让您知道发生了什么!
协程的优点是它们相对容易理解。您所犯的错误更容易预测。它们通常还需要较少的资源,这对您爬上成千上万的协程或线程的十分之一很有帮助。坦率的月亮在评论中提到,如果您没有正确研究线程,请坚持使用协程,它们是正确的。协程要简单得多。
线程完全是另一种野兽。您始终必须警惕其他线程随时可能中断您的可能性并弄乱您的数据。线程库提供了整套功能强大的工具来帮助您完成此任务,例如互斥锁和条件变量,它们可以帮助您告诉操作系统何时可以安全地运行其他线程之一以及何时不安全。整堂课程专门介绍如何正确使用这些工具。出现的著名问题之一是“死锁”,即两个线程都“卡住”,等待另一个线程释放一些资源时。另一个问题对于Unity而言非常重要,那就是许多库(例如Unity)并非旨在支持来自多个线程的调用。如果您不注意允许哪些调用和禁止哪些调用,则可以很容易地破坏您的框架。
这种额外复杂性的原因实际上非常简单。抢占式多任务处理模型实际上类似于多线程模型,该模型不仅允许您中断其他线程,而且还可以在不同的内核上并行运行线程。这是非常强大的功能,是真正利用即将推出的这些新四核和十六进制代码CPU的唯一方法,但是却打开了潘多拉魔盒。关于如何在多线程环境中管理此数据的同步规则非常残酷。在C ++世界中,有整篇文章专门MEMORY_ORDER_CONSUME
讨论多线程同步的一小部分。
那么线程的缺点?很简单:很难。您会遇到以前从未见过的所有错误。许多所谓的“ heisenbug”有时会出现,然后在调试它们时消失。为您提供的用于处理这些问题的工具非常强大,但是它们的级别也很低。它们被设计为在现代芯片的架构上高效,而不是易于使用。
但是,如果您想使用所有的CPU功能,它们就是您所需要的工具。此外,还有的居然算法更容易在多线程比他们与协程理解,只是因为你让OS手柄的地方中断可能发生的所有问题。
坦率的月亮评论坚持协程也是我的建议。如果您确实想要线程的强大功能,请提交它。正式出去真正地学习线程。我们已经花费了数十年的时间来弄清楚如何组织关于线程的最佳思考方式,以便您尽早获得安全可靠的可重复结果,并随需添加性能。例如,所有理智的课程将在讲授条件变量之前教互斥。所有涵盖原子的理智课程都将全面讲授互斥体和条件变量,甚至不提原子存在。(注意:没有关于原子的理智的教程。)尝试学习零碎的线程,您在乞求偏头痛。
join()
,但是您确实需要一些东西。如果您没有为系统设计同步系统的架构师,则必须自己编写。作为从事多线程工作的人,我发现人们关于计算机工作方式的思维模型需要进行调整,然后才能安全地进行同步(这是好的课程所教的内容)
join
。我的观点是,轻松实现线程可能带来的性能优势的中等程度有时可能比使用更复杂的线程方法获得更大的一部分更好。
用最简单的术语来说...
线程数
线程不决定何时产生线程,操作系统(“ OS”,例如Windows)决定线程产生的时间。操作系统几乎完全负责调度线程,它决定运行哪些线程,何时运行它们以及运行多长时间。
另外,一个线程可以同步运行(一个线程接一个线程)或异步运行(不同的线程运行在不同的CPU内核上)。异步运行的能力意味着线程可以在相同的时间内完成更多的工作(因为线程实际上是在同时做两件事)。如果操作系统擅长调度它们,那么即使同步线程也可以完成很多工作。
但是,这种额外的处理能力会带来副作用。例如,如果两个线程试图访问同一资源(例如列表),并且每个线程可以在代码中的任意位置随机停止,则第二个线程的修改可能会干扰第一个线程所做的修改。(另请参见:竞赛条件和死锁。)
线程也被认为是“繁重的”,因为它们有很多开销,这意味着在切换线程时会花费大量时间。
协程
与线程不同,协程是完全同步的,在任何时间点只能运行一个协程。另外,协程可以选择何时屈服,因此可以选择在方便的代码点(例如,在循环周期结束时)屈服。这样做的好处是,更容易避免种族条件和死锁之类的问题,并使协程协同工作更容易。
但是,这也是主要的责任,如果协程不能正确产生,则可能会消耗大量的处理器时间,并且如果不正确地修改共享资源,仍然可能会导致错误。
协程通常不需要上下文切换,因此可以快速切入和切出,并且非常轻巧。
综上所述:
线:
协程:
线程和协程的角色非常相似,但是它们在完成工作的方式上有所不同,这意味着它们各自更适合于不同的任务。线程最适合于任务,他们可以专注于自己做某事而不会被打断,然后在完成时发回信号。协程最适合可以通过许多小步骤完成的任务以及需要协同处理数据的任务。