这节记录一下ooc-lang中协程的使用。
协程是在线程中执行的,所以它不是并发执行的,而是非抢占式执行的,因此不用考虑多线程中内存共享和锁的问题。
线程中代码的执行是顺序的,协程是在线程中执行的,所以在执行一个协程的时候,其它地方的代码是不会执行的,除非协程执行完毕了或协程主动让出执行权限。
通俗地讲,协程就是执行一个函数,当然它是以一种比较特殊的方式执行的。我们把相关的功能封装到一个函数中,这个函数可能存在一些状态(按我的理解,状态就是记录一些属性的变量,比如函数开始执行时,它的初始状态下的局部变量为某一个值,执行一段时间后,它的状态变了,此时的变量的值可能为另一个值了),然后把这个函数交给协程执行。协程执行函数时的特殊地方在于:如果条件未满足,随时可以跳出这个函数,从而去执行另外的代码,然后在某个地方,条件满足后,又回到该协程中执行这个函数,此时该函数的状态仍保留着上一次执行后的,因此可以继续上一次的状态执行,而不是从初始状态执行。我们知道,每次调用一个函数,它里边的局部变量都是一种初始状态,当然函数里边调用的全局变量除外。
ooc-lang的源码本身是翻译成C语言的,所以ooc-lang的协程的底层也是用C语言实现的。当然,这里不看底层源码和实现逻辑。ooc-lang的协程源码在这里Coro.ooc,不多,不到百行。不多平台或不同语言的协程使用起来可能不一样,即使是同一语言的不同实现的协程,用法也可能不一样。像云风前辈用C实现的协程库,他的协程函数可以传入参数,以及可以获取协程的执行状态。我简单看了下U3D中,当然,它的又不一样。
下面我讲下ooc-lang的协程的特点:
ooc-lang的协程不接收任何参数,因此我们无法给协程的函数传参,这就有点不方便了,如果不同协程间要交互的话,比如在一个协程中启动另一个协程,此时我需要将某个变量的值传给它,就传不了,只能将变量声明为相对于协程之间为全局的了。
ooc-lang的协程的启动必须由另一个协程调用startCoro()
来启动,可以定义一个主协程,一般子协程都用主协程启动。主协程和子协程的区别就是:主协程不可以yield()
而子协程可以。
所有子协程都可以在函数内部通过调用yield()
来主动交出执行权限,所有协程都可以通过调用switchTo()
来切换并执行另一个协程。
ooc-lang的协程无法手动停止,得等它运行到设定的条件为止。不像U3D中的可以手动stop掉。
介绍完特点,再演示一个我写的简单例子看下吧。例子的模型是模拟一个场景,场景的主循环每隔一定时间就执行一遍,场景的事件是模拟一个餐厅中顾客点菜,服务员上菜。当然,这个场景并没有真正的画面,也与现实中的场景并不完全相符,只是给个简单的例子体会体会。
/*
* CoroDemo.ooc
*/
// 相关模块的引入
import os/[Coro, Time]
import structs/ArrayList
import math/Random
// 餐厅类
Restaurant: class {
// 餐厅的菜单
menu: static String[] = ["番茄炒蛋", "张全蛋", "张三李四王麻子"]
// 当前顾客点的菜
currentOrder := ""
// 餐厅中的顾客数量
numCustomer: Int
init: func(=numCustomer)
// 顾客点菜
customer: func {
index := Random randInt(0, 2)
currentOrder = menu[index]
"顾客点菜:#{currentOrder}" println()
}
// 服务员上菜
waiter: func {
"服务员上菜:#{currentOrder}" println()
}
}
main: func {
runTime := 0
startOrder := false
endOrder := false
// 创建并初始化一个主协程
mainCoro := Coro new(). initializeMainCoro()
// 实例话一个餐厅
restaurant := Restaurant new(5)
// 创建一个顾客协程
customerCoro := Coro new()
// 创建一个服务员协程
waiterCoro := Coro new()
// 模拟的场景主循环
while (true) {
// 游戏中肯定会有渲染逻辑和其它一些逻辑的执行的,这里只是简单的打印一下 :)
"\n[渲染场景]" println()
// 首次启动协程
if (!startOrder) {
// 通过主协程启动一个顾客协程,第一个参数是协程,第二个参数是一个闭包,也就是
// 参数一这个协程执行的函数了
mainCoro startCoro(customerCoro, ||
// 还没开始的话,先不执行后面的代码
if (!startOrder) customerCoro yield()
// 获取顾客数量
numCustomer := restaurant numCustomer
// 顾客依次点菜,知道所有顾客都点完
while (numCustomer > 0) {
restaurant customer()
--numCustomer
// 顾客点完菜,需等服务员上完菜,才能下一个顾客点菜
customerCoro yield()
}
// 注意这里,这句是切换回主协程的意思,如果没有这句,当子协程运行完后会直接退出整个程序
customerCoro switchTo(mainCoro)
)
// 通过主协程启动一个服务员协程,参数与和上面介绍的一致
// 原理也和顾客协程查不多,就不多说了
mainCoro startCoro(waiterCoro, ||
if (!startOrder) waiterCoro yield()
numCustomer := restaurant numCustomer
while (numCustomer > 0) {
restaurant waiter()
--numCustomer
waiterCoro yield()
}
"点菜完毕" println()
endOrder = true
waiterCoro switchTo(mainCoro)
)
startOrder = true
}
// 如果顾客没有点完菜,每跑一次主循环都切回子协程执行,直到子协程执行完毕
// 为了不影响游戏逻辑,一般协程的执行都是等游戏逻辑执行完再执行的吧?毕竟协程实在同一个线程中执行的。不过这个我也还不太清楚
if (!endOrder) {
mainCoro switchTo(customerCoro)
mainCoro switchTo(waiterCoro)
}
// 主循环会运行8次
if (runTime > 8) {
break
} else {
runTime += 1
}
// 每次运行都间隔1秒钟
Time sleepMilli(1000)
}
}
这是运行截图:
可以看到顾客点了5个菜,服务员也上了5个菜。点完菜后,主循环继续执行了几遍。
协程的基本原理和使用就差不多是这样了。全文完 ?