【lua学习】8.协程

1 数据结构和宏

1.1 协程的状态码

(lua.h)

#define LUA_YIELD	1 //挂起
#define LUA_ERRRUN	2 //运行时错误
#define LUA_ERRSYNTAX	3 //语法错误
#define LUA_ERRMEM	4 //内存错误
#define LUA_ERRERR	5 //在错误处理函数中出错

1.2 协程的执行状态码

(lbaselib.c)

#define CO_RUN	0 //执行中状态
#define CO_SUS	1 //挂起状态
#define CO_NOR	2 //常规状态(这个协程唤醒了另一个协程)
#define CO_DEAD	3 //死亡状态

//协程执行状态码对应的字符串
static const char *const statnames[] =
    {"running", "suspended", "normal", "dead"};

1.3 lua_State 协程结构体

(lstate.h) lua_State

struct lua_State
{
	CommonHeader;//#define CommonHeader	GCObject *next; lu_byte tt; lu_byte marked
	lu_byte status;//协程的状态码
	StkId top;//栈顶位置,“寄存器”第一个可用位置
	StkId base;//当前函数调用的栈基址
	global_State* l_G;//全局状态机
	CallInfo* ci;//当前函数调用信息
	const Instruction* savedpc;//指令指针
	StkId stack_last;//“寄存器”最后一个可用位置
	StkId stack;//栈数组的起始位置
	CallInfo* end_ci;//函数调用信息数组的最后一个位置的下一个位置
	CallInfo* base_ci;//函数调用信息数组首地址
	int stacksize;//栈的大小
	int size_ci;//函数调用信息数组大小
	unsigned short nCcalls;//内嵌C调用的层数
	unsigned short baseCcalls;//唤醒协程时的内嵌C调用层数
	lu_byte hookmask;
	lu_byte allowhook;
	int basehookcount;
	int hookcount;
	lua_Hook hook;
	TValue l_gt;//Global表
	TValue env;//环境表的临时位置
	GCObject* openupval;//栈上open状态的uvalues
	GCObject* gclist;
	struct lua_longjmp* errorJmp;//当前跳转信息,实现try catch的关键结构
	ptrdiff_t errfunc;//当前错误处理函数相对于“寄存器数组首地址”的偏移地址
};

2 C API

2.1 lua_newthread 新建一个协程,压栈,返回这个新协程

(lapi.c) lua_newthread

LUA_API lua_State* lua_newthread(lua_State* L)
{
	luaC_checkGC(L);
	lua_State* L1 = luaE_newthread(L);
	setthvalue(L, L->top, L1);
	api_incr_top(L);//#define api_incr_top(L) { L->top++; }
	return L1;
}

(lstate.c) luaE_newthread 新建一个协程并返回这个协程

lua_State* luaE_newthread(lua_State* L)
{
	//#define tostate(l) (cast(lua_State *, cast(lu_byte *, l) + LUAI_EXTRASPACE))
	//#define state_size(x)	(sizeof(x) + LUAI_EXTRASPACE)
	lua_State* L1 = tostate(luaM_malloc(L, state_size(lua_State)));
	luaC_link(L, obj2gco(L1), LUA_TTHREAD);
	//初始化协程
	preinit_state(L1, G(L));
	//栈初始化(初始化 CallInfo 数组,“寄存器” 数组,第一个CallInfo)
	stack_init(L1, L);
	//所有的协程的Global表都是用的主协程的Global表哦
	setobj2n(L, gt(L1), gt(L));
	L1->hookmask = L->hookmask;
	L1->basehookcount = L->basehookcount;
	L1->hook = L->hook;
	resethookcount(L1);//#define resethookcount(L) (L->hookcount = L->basehookcount)
	return L1;
}

2.2 luaE_freethread 释放一个协程L1

(lstate.c)

void luaE_freethread(lua_State* L, lua_State* L1)
{
	//关闭协程L1上所有upvalue
	luaF_close(L1, L1->stack);
	//释放协程L1上“寄存器”数组和CallInfo数组
	freestack(L, L1);
	//释放协程L1本身的内存
	//#define luaM_freemem(L, b, s)	luaM_realloc_(L, (b), (s), 0)
	//#define fromstate(l)	(cast(lu_byte *, (l)) - LUAI_EXTRASPACE)
	//#define state_size(x)	(sizeof(x) + LUAI_EXTRASPACE)
	luaM_freemem(L, fromstate(L1), state_size(lua_State));
}

2.3 lua_status 获取协程的错误码

(lapi.c) lua_status

LUA_API int lua_status(lua_State* L)
{
	return L->status;
}

2.4 lua_resume 唤醒一个协程,nargs为参数个数,返回值为协程状态码

(ldo.c) lua_resume

LUA_API int lua_resume(lua_State* L, int nargs)
{
	if (L->status != LUA_YEILD && (L->status != 0 || L->ci != L->base_ci))
	{
		return resume_error(L, "cannot resume non-suspend coroutine");
	}
	if (L->nCcalls >= LUAI_MAXCCALLS)
	{
		return resume_error(L, "C stack overflow");
	}
	L->baseCcalls = ++L->nCcalls;
	int status = luaD_rawrunprotected(L, resume, L->top - nargs);
	if (status != 0)
	{
		//记录状态码
		L->status = cast_byte(status);
		//根据状态码status将错误入栈
		luaD_seterrorobj(L, status, L->top);
		L->ci->top = L->top;
	}
	else
	{
		status = L->status;
	}
	--L->nCcalls;
	return status;
}

(ldo.c) resume_error 将错误信息msg压栈

static int resume_error(lua_State* L, const char* msg)
{
	L->top = L->ci->top;
	setsvalue2s(L, L->top, luaS_new(L, msg));
	incr_top(L);
	return LUA_ERRRUN;
}

(ldo.c) resume 唤醒协程

static void resume(lua_State* L, void* ud)
{
	StkId firstArg = cast(StkId, ud);
	CallInfo* ci = L->ci;
	//若状态码为0,则执行函数
	if (L->status == 0)
	{
		//若是C函数,直接结束,因为若是C函数那么在luaD_precall里面就执行过了
		if (luaD_precall(L, fisrtArg - 1, LUA_MULTRET) != PCRLUA)
		{
			return;
		}
	}
	else
	{
		L->status = 0;
		//若是C函数
		//#define ci_func(ci) (clvalue((ci)->func))
		//#define f_isLua(ci) (!ci_func(ci)->c.isC)
		if (!f_isLua(ci))
		{
			//函数收尾工作。若函数实际返回值数量!=函数原型返回值数量,则调整栈顶
			if (luaD_poscall(L, firstArg))
			{
				L->top = L->ci->top;
			}
		}
		//若是lua函数,恢复函数调用栈基址
		else
		{
			L->base = L->ci->base;
		}
	}
	//继续执行指令
	luaV_execute(L, cast_int(L->ci - L->base_ci));
}

2.5 lua_yield 挂起一个协程

(ldo.c) lua_yield

LUA_API int lua_yield(lua_State* L, int nresults)
{
	if (L->nCcalls > L->baseCcalls)
	{
		luaG_runerror(L, "attempt to yield across metamethod/C-call boundary");
	}
	//调整函数调用的栈基址
	L->base = L->top - nresults;
	//标记协程状态为LUA_YIELD
	L->status = LUA_YIELD;
	return -1;
}

3 Lua API

3.1 协程模块的注册

3.1.1 luaL_register(L, LUA_COLIBNAME, co_funcs) 注册协程模块

(lbaselib.c) luaopen_base

LUALIB_API int luaopen_base(lua_State* L)
{
	base_open(L);//base_open见模块章节
	//注册协程模块,LUA_COLIBNAME为模块名,co_funcs为库函数列表
	//#define LUA_COLIBNAME	"coroutine"
	luaL_register(L, LUA_COLIBNAME, co_funcs);//luaL_register见模块章节
	return 2;
}

3.1.2 co_funcs 注册的协程库函数列表

(lbaselib.c)

static const luaL_Reg co_funcs[] = {
	{"create", luaB_cocreate},
	{"resume", luaB_coresume},
	{"running", luaB_corunning},
	{"status", luaB_costatus},
	{"wrap", luaB_cowrap},
	{"yield", luaB_yield},
	{NULL, NULL}
};

3.2 luaB_cocreate 创建一个协程,协程留在栈顶

(lbaselib.c) luaB_cocreate

static int luaB_cocreate(lua_State* L)
{
	//新建一个协程NL
	lua_State* NL = lua_newthread(L);
	//将函数移到栈顶(前提:函数已经就是第一个参数了)
	lua_pushvalue(L, 1);
	//将函数从协程L移到协程NL
	lua_xmove(L, NL, 1);
	return 1;
}

3.3 luaB_costatus 获取当前协程的状态,将状态字符串压栈

(lbaselib.c) luaB_costatus

static int luaB_costatus(lua_State* L)
{
	lua_State* co = lua_tothread(L, 1);
	lua_pushstring(L, statnames[costatus(L, co)]);
	return 1;
}

(lbaselib.c) costatus 获取协程的状态码

static int costatus(lua_State* L, lua_State* co)
{
	//若L==co,必定是 执行中状态
	if (L == co)
	{
		return CO_RUN;
	}
	//根据co的状态码,来决定其运行状态
	switch (lua_status(co))
	{
		//挂起状态
		case LUA_YIELD:
		{
			return CO_SUS;
		}
		//若为0,看情况
		case 0:
		{
			lua_Debug ar;
			//若还有栈帧,则说明是normal状态
			if (lua_getstack(co, 0, &ar) > 0)
			{
				return CO_NOR;
			}
			//否则,若无参数,则说明协程已死
			else if (lua_gettop(co) == 0)
			{
				return CO_DEAD;
			}
			//其余情况,则为挂起状态
			else
			{
				return CO_SUS;
			}
		}
		//其余情况则为死亡状态
		default:
		{
			return CO_DEAD;
		}
	}
}

(ldebug.c) lua_getstack 获取第level层函数调用的栈的状态

LUA_API int lua_getstack(lua_Stack* L, int level, lua_Debug* ar)
{
	//寻找 满足 level==0 或者 为base_ci 的 CallInfo
	for (CallInfo* ci = L->ci; level > 0 && ci > L->base_ci; ci--)
	{
		level--;
		//#define ci_func(ci) (clvalue((ci)->func))
		//#define f_isLua(ci) (!ci_func(ci)->c.isC)
		if (f_isLua(ci))
		{
			level -= ci->tailcalls;
		}
	}
	if (level == 0 && ci > L->base_ci)
	{
		status = 1;
		ar->i_ci = cast_int(ci -> L->base_ci);
	}
	else if (level < 0)
	{
		status = 1;
		ar->i_ci = 0;
	}
	//ci == L->base_ci
	else
	{
		status = 0;
	}
	return status;
}

3.4 luaB_coresume 唤醒一个协程,栈顶为[true值,协程yield或return的值] 或者为[false值,,错误信息]

(lbaselib.c) luaB_coresume

static int luaB_coresume(lua_State* L)
{
	//协程为第一个参数,传给协程的参数个数为lua_gettop(L) - 1
	lua_State* co = lua_tothread(L, 1);
	//唤醒协程,r若<0则表示出错,r>=0表示 协程yield或return的值的数量
	int r = auxresume(L, co, lua_gettop(L) - 1);
	if (r < 0)
	{
		//错误信息在栈顶,false值插入错误信息下
		lua_pushboolean(L, 0);
		lua_insert(L, -2);
		return 2;
	}
	else
	{
		//true值插入r个返回值之下
		lua_pushboolean(L, 1);
		lua_insert(L, -(r + 1));
		return r + 1;
	}
}

(lbaselib.c) auxresume 恢复协程co,参数narg个,返回值为 协程yield或return的值的数量

static int auxresume(lua_State* L, lua_State* co, int narg)
{
	int status = costatus(L, co);
	//若栈无 需要参数个数 的空闲空间,且扩容失败,则报错
	if (!lua_checkstack(co, narg))
	{
		luaL_error(L, "too many arguments to resume");
	}
	//co不是挂起状态,则报错
	if (status != CO_SUS)
	{
		lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
		return -1;
	}
	//将协程L栈顶的narg个值出栈,移到协程co的栈顶
	lua_xmove(L, co, narg);
	//将协程L的C调用层数 赋值给 协程co的C调用层数
	lua_setlevel(L, co);//co->nCcalls = L->nCcalls;
	status = lua_resume(co, narg);
	if (status == 0 || status == LUA_YIELD)
	{
		int nres = lua_gettop(co);
		if (!lua_checkstack(L, nres + 1))
		{
			luaL_error(L, "too many results to resume");
		}
		//将协程co yeild的返回值压入 协程L的栈顶
		lua_xmove(co, L, nres);
		return nres;
	}
	else
	{
		//将栈顶的错误信息从协程co移到协程L
		lua_xmove(co, L, 1);
		return -1;
	}
}

3.5 luaB_corunning 获取当前正在执行的协程,压栈

(lbaselib.c) luaB_corunning

static int luaB_corunning(lua_State* L)
{
	//将L放在L的栈顶
	if (lua_pushthread(L))//若 G(L)->mainthread == L 为true
	{
		lua_pushnil(L);//若为主协程,则返回nil。因为lua用的不对称协程,所以主协程不能当作一般的协程看待。
	}
	return 1;
}

3.6 luaB_cowrap 创建一个协程和一个C闭包,并以该协程作为该C闭包的upvalue,将该C闭包压栈

(lbaselib.c) luaB_cowrap

static int luaB_cowrap(lua_State* L)
{
	luaB_cocreate(L);
	lua_pushcclosure(L, luaB_auxwrap, 1);
	return 1;
}

(lbaselib.c) luaB_auxwrap 协程的包装函数

static int luaB_auxwrap(lua_State* L)
{
	lua_State* co = lua_tothread(L, lua_upvalueindex(1));
	//唤醒协程
	int r = auxresume(L, co, lua_gettop(L));
	if (r < 0)
	{
		//若栈顶是字符串类型,则栈顶的为错误信息
		if (lua_isstring(L, -1))
		{
			luaL_where(L, 1);//获取堆栈信息
			lua_insert(L, -2);//插入错误信息之前
			lua_concat(L, 2);//连接错误信息
		}
		lua_error(L);//传递错误信息
	}
	return r;
}

3.7 luaB_yield 挂起协程

(lbaselib.c) luaB_yield

static int luaB_yield(lua_State* L)
{
	return lua_yield(L, lua_gettop(L));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值