协程的概念听起来很有趣,但是我不知道在实际的生产环境中是否有意义?哪些情况下使用协程实现更加优雅、简单或者高效呢?
一个使用场景是具有多个同时连接、需要在所有连接中并行调度读写的Web服务器。
这可以使用协程来实现。每个连接都是一个协程,它读取/写入一些数据,然后将控制权交给调度程序。调度程序传递给下一个协程(执行相同的操作),循环遍历所有连接。
应用场景:协程经常在游戏编程中用于时间分片计算。
为了保持游戏的一致帧率,例如60 fps,在每个帧中您大约有16.6ms来执行代码。这包括物理模拟、输入处理、绘制/绘画等操作。
假设您的方法在每个帧中都要执行。如果您的方法执行时间很长并跨越多个帧,则会导致游戏循环中的其余计算出现停滞,这将导致用户看到“卡顿”(帧速率突然下降)。
协程使得可以对计算进行时间分片,以便它在每个帧中都能运行一点点。
为此,协程允许该方法“yield”(暂停)计算,并将其返回给“调用者”(在本例中为游戏循环),以便在下次调用该方法时它可以从离开的地方继续执行。
Unix管道是一个使用案例:
grep TODO *.c | wc -l
grep
命令生成一系列行并将它们写入缓冲区。wc
命令从缓冲区读取这些行。如果缓冲区填满,则 grep
会“阻塞”,直到缓冲区为空。如果缓冲区为空,则 wc
等待缓冲区中的更多输入。真正的协程需要语言支持。它们需要由编译器实现,并由底层框架支持。
协程的一种基于语言支持的实现是C# 2.0中的yield return
关键字,它允许您编写一个返回多个值以进行循环的方法。
然而,yield return
有限制。该实现使用助手类来捕获状态,并且仅支持将协程作为生成器(迭代器)的特定情况。
在更一般的情况下,协程的优点在于它们使某些基于状态的计算更易于表达和理解。例如,将状态机实现为一组协程可能比其他实现更简洁。但是,这样做需要C#或Java尚不存在的语言支持。
协程非常有用,可用于实现生产者/消费者模式。
例如,Python引入了协程的语言特性称为生成器,旨在简化迭代器的实现。
协程还可用于实现协作式多任务处理,其中每个任务都是一个协程,会让出控制权给调度器/反应堆。
当系统执行需要大量等待的长时间运行步骤,且包含两个或多个任务时,协程(coroutines)非常有用。
例如,考虑一个设备,它具有LCD和键盘用户界面以及调制解调器,并且需要使用调制解调器定期呼叫并报告其状态,而与键盘上的用户所做的操作无关。编写用户界面的最佳方式可能是使用像“input_numeric_value(&CONV_SPEED_FORMAT, &conveyor_speed);”这样的函数,该函数将在用户输入值时返回,并处理通信的最好方法可能是使用"wait_for_carrier();"这样的函数,该函数将在单元连接或确定不连接时返回。
如果没有使用协程,则UI子系统或调制解调器子系统必须使用状态机实现。使用协程可以使得两个子系统都以最自然的方式编写。请注意,重要的是,两个子系统都不能长时间处于不一致的状态并调用yield(),也不能在调用yield()之前不把事情放入“一致”状态。但通常很容易满足这些约束。
请注意,虽然可以使用全面的多任务处理,但这需要几乎在修改共享状态的任何地方都广泛使用锁或其他互斥构造。由于协程切换器除了在yield()调用时,永远不会切换事物,因此任何一个例程都可以自由地修改共享状态,只要它确保在下一次yield之前一切有序,并做好准备让另一个例程在yield() "期间" 修改状态。
printf(“位置为(%d,%d)”,x,y);
的内容,并且不会阻塞其他任务的执行。而使用状态机,则需要... - supercat一个使用场景:内存数据库系统。
协程可用于减少因在此类数据库系统中进行查找(如哈希探测或B+树遍历)而导致的CPU停顿,从而提高性能。
数据库系统使用许多基于指针的数据结构,包括哈希表和B+树,这些结构需要在哈希探测或B+树遍历期间进行大量的“指针追踪”。当一批操作同时到达时,协程可以轻松地在操作之间交错指令流,从而减少由缓存未命中引起的CPU停顿,并利用内存访问的并行性。