[ooc-lang]协程的使用及一些要点

这节记录一下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个菜。点完菜后,主循环继续执行了几遍。

协程的基本原理和使用就差不多是这样了。全文完 ?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值