协程有哪些好处?

我一直在学习一些用于游戏开发的Lua。我听说其他语言中有协程(coroutines)的概念,但是在Lua中才真正了解了它们。我并不真正理解它们有多有用,我听说了很多有关它们可以实现多线程事物的讨论,但是它们不是按顺序运行吗?那么,与按顺序运行的普通函数相比,有什么好处呢?对我来说,我还没有完全理解它们与函数之间的不同之处,除了它们可以暂停并让另一个函数运行一秒钟。似乎对我来说用例的场景并不会那么大。

有人能解释一下为什么有人从中受益吗?

尤其是从游戏编程的角度来看,有更深入的见解就好了^^

原文链接 https://stackoverflow.com/questions/7224625

点赞
stackoverflow用户803263
stackoverflow用户803263

我们在一个我正在工作的项目中使用它们。对我们来说主要的好处是,有些时候由于某些依赖关系,在异步代码中有一些重要的部分需要按顺序运行。如果你使用协程,你可以强制一个进程等待另一个进程完成。它们并不是做到这一点的唯一方法,但它们可能比其他一些方法简单得多。

2011-08-28 23:13:16
stackoverflow用户298661
stackoverflow用户298661

我只是不理解他们与函数的不同之处,除了它们可以暂停并让另一个运行一秒钟。 这是一个非常重要的属性。我曾经在一个游戏引擎上工作,它使用它们来计时。例如,我们有一个引擎,以每秒10个tick的速度运行,你可以等待x个tick的WaitTicks(x),在用户层中,你可以运行WaitFrames(x)等待x帧。即使专业的原生并发库也会使用相同类型的yielding行为。

2011-08-28 23:18:19
stackoverflow用户734069
stackoverflow用户734069

好的,从游戏开发的角度思考。

假设你正在制作一段切场动画或教程。无论哪种情况,你都有一个有序的命令序列发送到某些实体。一个实体移动到一个位置,和一个人说话,然后走别的地方。以此类推。一些命令必须等待其他命令完成才能开始。

现在回顾一下游戏的工作方式。每一帧,它必须处理AI、碰撞测试、动画、渲染和声音等等。你只能每帧思考一次。那么你如何将这种代码放入其中,你必须等待某些操作完成才能进行下一个操作?

如果你用C++构建了一个系统,你会有一个在AI之前运行的东西。它将有一个要处理的命令序列。其中一些指令是瞬间完成的,比如“告诉实体X去这里”或“在这里生成实体Y”。其他的则必须等待,比如“告诉实体Z去这里,在它到达这里之前不要处理任何其他命令。”命令处理器必须在每一帧调用,它必须理解“实体在位置”等复杂条件。

在Lua中,它看起来像这样:

local entityX = game:GetEntity("entityX");
entityX:GoToLocation(locX);
local entityY = game:SpawnEntity("entityY", locY);
local entityZ = game:GetEntity("entityZ");
entityZ:GoToLocation(locZ);
do
  coroutine.yield();
until (entityZ:isAtLocation(locZ));
return;

在C++方面,你需要每帧恢复一次这个脚本,直到完成为止。一旦返回,你就知道切场动画结束了,所以你可以将控制权返回给用户。

看看Lua逻辑多么简单明了。它完全按照它所说的那样工作。它清晰、明显,因此非常难出错。

协同程序的威力在于能够部分完成某个任务,等待条件变为真,然后移动到下一个任务。

2011-08-28 23:19:53
stackoverflow用户596285
stackoverflow用户596285

大量适用于游戏开发者的好例子。我将给出另一个应用扩展空间的例子。考虑一个应用程序引擎,能够在执行核心功能的 C 语言中运行用户的 Lua 代码。如果用户需要等待引擎达到特定状态(例如等待数据接收),则可以进行以下处理:

  • 多线程 C 程序以在单独的线程中运行 Lua,并添加锁定和同步方法,
  • 异常 Lua 程序并使用传递给函数的状态尝试重新启动,以跳过任何应该仅运行一次的代码,或者
  • 暂停 Lua 程序,并在 C 中达到状态后恢复它。

第三个选项对我来说是最简单的实现方式,避免了在多个平台上处理多线程的需要。这还允许用户的代码运行不受修改,看起来就像他们调用的函数需要很长时间才能完成。

2011-08-29 00:14:45
stackoverflow用户1505148
stackoverflow用户1505148

协程在游戏中的使用:

使用简单,但是在多个地方同时使用容易出错。

要小心,不要在太多地方使用协程。 不要使整个 AI 代码依赖于协程。

协程适合在引入之前不存在的状态时进行快速修复。

这正是 Java 所做的。Sleep() 和 Wait() 这两个函数是让您的游戏无法调试的最佳方式。 如果我是您,我会完全避免任何必须使用像协程一样的 Wait() 函数的代码。

OpenGL API 是您应该注意的东西。它从不使用 wait() 函数,而是使用一个干净的状态机,知道每个对象的状态在哪个状态下。 如果您使用协程,你将会产生许多无状态的代码块,这肯定会让调试变得非常困难。

当您制作类似于文本编辑器、银行应用程序、服务器、数据库等应用程序时,协程是很好的(不适用于游戏)。 但是当您制作可以在任何时候发生任何事情的游戏时,协程就不太适合了,您需要有状态。

所以,在我的看来,协程是一种糟糕的编程方式,是写小型无状态代码的借口。

但这只是我的看法。

2013-03-19 20:56:17
stackoverflow用户1412279
stackoverflow用户1412279

这更像是一种信仰。有些人相信协程,有些人则不信。使用情况、实现方式和环境的综合效果才是关键。

不要相信基准测试结果,试图证明在多核 CPU 上使用协程比在单线程中使用循环更快:如果协程比循环慢,那就太丢人了!如果这个运行环境后来在所有核心始终处于负载状态的硬件上运行,它就会变得更慢,出现问题...

所以,它本身并没有什么好处。

有时它很方便。但如果你最终得到了大量的协程、出现了状态改变并失去了范围,那你会咒骂协程。但至少这并不是协程框架的错,还是你自己的问题。

2021-10-20 08:32:42