coroutine的概念听起来非常有趣,但我不知道,如果它在真正的生产环境中有意义吗?协同程序的用例是什么,与其他方法一样,可以解决更优雅,更简单或更高效的问题?
答案 0 :(得分:48)
描述协程的一些好答案。
但是对于实际的用例。拿一个Web服务器。它有多个同时连接,它想安排读写所有连接。
这可以使用协程来实现。每个连接都是一个协程,它读取/写入少量数据,然后“调出”控制到调度程序,调度程序在我们循环遍历所有可用连接时传递给下一个协同程序(它做同样的事情)。
答案 1 :(得分:21)
很多,例如:
grep TODO *.c *.h | wc -l
上面的管道正是一个协程:grep
命令生成一系列行到缓冲区的行,wc
命令“吃掉它们”;如果缓冲区填满,则grep
“阻塞”直到缓冲区清空,如果缓冲区为空,则wc
命令等待新输入。
关于协同程序的事情是它们现在最常用于更多约束模式,如提到的Python生成器,或者作为管道。
如果您想更多地了解它们,请参阅维基百科文章,尤其是coroutines和iterators。
答案 2 :(得分:18)
真正的协同程序需要您的工具支持 - 它们需要由编译器实现并由底层框架支持。
使用C#2.0中提供的“yield return”关键字找到Coroutines的一个真实示例,它允许您编写一个返回多个循环值的方法。
“yield return”确实有局限性,但是 - 实现使用辅助类来捕获状态,它只支持一个特定的coroutine作为生成器(迭代器)。
在更一般的情况下,Coroutines的优势在于它们使某些基于状态的计算更容易表达和更容易理解 - 将状态机实现为一组协程可以比更常见的方法更优雅即可。但是,这样做需要C#或Java中尚不存在的支持和工具。
答案 3 :(得分:18)
我知道问题被问到这已经差不多5年了,但我很惊讶没有人提到游戏的用例,其中协同程序被大量用于实际计算时间。
为了在游戏中保持一致的帧速率,假设为60 fps,每帧中执行代码大约需要16.6ms。这包括物理模拟,输入处理,绘图/绘画。
让我们说你的方法是在每一帧中执行的。如果你的方法花费很长时间并最终跨越多个帧,你将在游戏循环中错开其余的计算,这导致用户看到“jank”(帧速率突然下降)。
你可以做什么协同程序以某种方式对这个计算进行时间切片,以便它在每一帧中运行一点点。
为了实现这一点,协程实际上允许该方法将计算“产生”回“调用者”(在这种情况下是游戏循环),以便下次调用该方法时,它从中断处继续。
答案 4 :(得分:11)
协程可用于实现生产者/消费者模式。
例如,Python在名为generators的语言功能中引入了协同程序,旨在简化迭代器的实现。
它们对于实现协作式多任务处理也很有用,其中每个任务都是一个可以产生调度程序/反应堆的协程。
答案 5 :(得分:9)
当系统有两个或多个代码时,协程会很有用,其最自然的表示形式是一系列连续的步骤,需要大量的等待。
例如,考虑一个具有LCD和键盘用户界面和调制解调器的设备,它需要使用调制解调器定期调用和报告其状态,而与键盘上的用户正在做什么无关。编写用户界面的最好方法可能是使用“input_numeric_value(& CONV_SPEED_FORMAT,& conveyor_speed)”等功能。当用户输入值时将返回,并且处理通信的最佳方式可能是使用“wait_for_carrier();”之类的函数。当装置连接或确定它不会返回时,它将返回。
如果没有协同程序,则必须使用状态机来实现UI子系统或调制解调器子系统。使用协程可以使两个子系统以最自然的方式编写。请注意,重要的是,如果不将事物置于“一致”状态并调用yield(),也不调用yield()而不将事物置于“一致”状态,那么子系统都不会长时间运行,但通常不难满足这些约束
请注意,虽然可以使用完整的多任务处理,但只要共享状态发生变化,就需要在整个地方使用锁。由于协程切换器除了在yield()调用之外不会切换任何东西,所以任何例程都可以自由地改变共享状态,只要它确保在下一次产生之前按顺序排列所有内容,并为其他例程改变状态做好准备“在“收益率”()期间。
答案 6 :(得分:6)
作为生产者/消费者行中一个更具体的例子,像简陋的批量报告程序这样简单的东西实际上可以使用协同例程。
该示例的关键提示是消耗输入数据(例如解析数据或累积帐户上的费用和付款)以及产生输出的非平凡工作。当你有这些特征时:
然后协同程序和队列都是很好的技术供您使用。