关闭

Lua知识点3_线程和状态

112人阅读 评论(0) 收藏 举报
分类:
以下是在cocos2dx-3.10 lua中


1.多线程
Lua不支持真正的多线程,而是一种协作式的多线程,彼此之间协作完成,并不是抢占完成任务,由于这种协作式的线程,
因此可以避免由不可预知的线程切换所带来的问题;另一方面,Lua的多个状态之间不共享内存,这样便为Lua中的并发
操作提供了良好的基础。


从C API的角度来看,将线程想象成一个栈可能更形象些。从实现的观点来看,一个线程的确就是一个栈。每个栈都保留
着一个线程中所有未完成的函数调用信息,这些信息包括调用的函数、每个调用的参数和局部变量。也就是说,一个栈拥
有一个线程得以继续运行的所有信息。因此,多个线程就意味着多个独立的栈。


当调用Lua C API中的大多数函数时,这些函数都作用于某个特定的栈。当我们调用lua_pushnumber时,就会将数字
压入一个栈中,那么Lua是如何知道该使用哪个栈的呢?答案就在类型lua_State中。这些C API的第一个参数不仅表示
了一个Lua状态,还表示了一个记录在该状态中的线程。


只要创建一个Lua状态,Lua就会自动在这个状态中创建一个新线程,这个线程称为“主线程”。主线程永远不会被回收。
当调用lua_close关闭状态时,它会随着状态一起释放。调用lua_newthread便可以在一个状态中创建其他的线程。
lua_State * lua_newthread ( lua_State * L ) ;


这个函数返回一个lua_State指针,表示新建的线程。它会将新线程作为一个类型为“thread”的值压入栈中。如果
我们执行了:
L1 = lua_newthread ( L ) ;


现在,我们拥有了两个线程L和L1,它们内部都引用了相同的Lua状态。每个线程都有其自己的栈。新线程L1以一个
空栈开始运行,老线程L的栈顶就是这个新线程。


除了主线程以外,其它线程和其它Lua对象一样都是垃圾回收的对象。当新建一个线程时,线程会压入栈,这样能确
保新线程不会成为垃圾,而有的时候,你在处理栈中数据时,不经意间就把线程弹出栈了,而当你再次使用该线程时,
可能导致找不到对应的线程而程序崩溃。为了避免这种情况的发生,可以保持一个对线程的引用,比如在注册表中保
存一个对线程的引用。


当拥有了一个线程以后,我们就可以像主线程那样来使用它,以前博文中提到的对栈的操作,对这个新的线程都适用。
然而,使用多线程的目的不是为了实现这些简单的功能,而是为了实现协同程序。


为了挂起某些协同程序的执行,并在稍后恢复执行,我们可以使用lua_resume函数来实现。
int lua_resume ( lua_State * L , int narg ) ;


lua_resume可以启动一个协同程序,它的用法就像lua_call一样。将待调用的函数压入栈中,并压入其参数,最后
在调用lua_resume时传入参数的数量narg。这个行为与lua_pcall类似,但有3点不同。
(1)lua_resume没有参数用于指出期望的结果数量,它总是返回被调用函数的所有结果;
(2)它没有用于指定错误处理函数的参数,发生错误时不会展开栈,这就可以在发生错误后检查栈中的情况;
(3)如果正在运行的函数交出(yield)了控制权,lua_resume就会返回一个特殊的代码LUA_YIELD,并将线程置于
一个可以被再次恢复执行的状态。


当lua_resume返回LUA_YIELD时,线程的栈中只能看到交出控制权时所传递的那些值。调用lua_gettop则会返回这
些值的数量。若要将这些值移到另一个线程,可以使用lua_xmove。


为了恢复一个挂起线程的执行,可以再次调用lua_resume。在这种调用中,Lua假设栈中所有的值都是由yield调用
返回的,当然了,你也可以任意修改栈中的值。作为一个特例,如果在一个lua_resume返回后与再次调用lua_resume
之间没有改变过线程栈中的内容,那么yield恰好返回它交出的值。


现在,我就通过一个简单的程序来做个试验,以便更好的理解Lua的线程。使用C代码来调用Lua脚本,Lua函数作为一
个协同程序来启动,这个Lua函数可以调用其它Lua函数,任意的一个Lua函数都可以交出控制权,从而使lua_resume
调用返回。
Lua代码===================
function Func1 ( param1 )
Func2 ( param1 + 10 )
print ( "Func1 ended." )
return 30
end


function Func2 ( value )
coroutine . yield ( 10 , value )
print ( "Func2 ended." )
end


C++代码===================
lua_State * L1 = lua_newthread ( L ) ;
if ( ! L1 )
return 0 ;


lua_getglobal ( L1 , "Func1" ) ;
lua_pushinteger ( L1 , 10 ) ;


// 运行这个协同程序
// 这里返回LUA_YIELD
bRet = lua_resume ( L1 , 1 ) ;
cout << "bRet:" << bRet << endl ;


// 打印L1栈中元素的个数
cout << "Element Num:" << lua_gettop ( L1 ) << endl ;


// 打印yield返回的两个值
cout << "Value 1:" << lua_tointeger ( L1 , - 2 ) << endl ;
cout << "Value 2:" << lua_tointeger ( L1 , - 1 ) << endl ;


// 再次启动协同程序
// 这里返回0
bRet = lua_resume ( L1 , 0 ) ;
cout << "bRet:" << bRet << endl ;
cout << "Element Num:" << lua_gettop ( L1 ) << endl ;
cout << "Value 1:" << lua_tointeger ( L1 , - 1 ) << endl ;


上面的例子是C语言调用Lua代码,Lua可以自己挂起自己;C函数不能自己挂起它自己,一个C函数只有在返回时,才会交出控制权。
因此C函数实际上是不会停止自身执行的,不过它的调用者可以是一个Lua函数,那么这个C函数调用lua_yield,就可以挂起Lua调用者:
int lua_yield ( lua_State * L , int nresults ) ;


你没有听错,C代码调用lua_yield不能挂起自己,但是它却可以将它的Lua调用者挂起。其中nresults是准备返回给
相应resume的栈顶值的个数,当协同程序再次恢复执行时,Lua调用者会收到传递给resume的值。lua_yield在使用时,
只能作为一个返回的表达式,而不能独自使用。比如:
return lua_yield ( L , 0 ) ;




Lua代码===================
require "lua_yieldDemo"


local function1 = function ( )
local value
repeat
value = Module.Func1 ( )
until value


return value
end


local thread1 = coroutine.create ( function1 )


-- 现在运行到了Module.Func1()
-- 100这个值将会被赋值给value
coroutine . resume ( thread1 )


--print(coroutine.status(thread1))
-- 设置C函数环境
Module.Func2 ( 10 )
print ( coroutine.resume ( thread1 ) )


C代码===================
// 判断环境表中JellyThink是否被设置了
static int IsSet ( lua_State * L ){
lua_getfield ( L , LUA_ENVIRONINDEX , "JellyThink" ) ;
if ( lua_isnil ( L , - 1 ) ){
printf ( "Not set\n" ) ;
return 0 ;
}
return 1 ;
}


static int Func1 ( lua_State * L ){
// 没有被设置就挂起
if ( ! IsSet ( L ) ){
printf ( "Begin yield\n" ) ;
return lua_yield ( L , 0 ) ;
}


// 被设置了,就取值,返回被设置的值
printf ( "Resumed again\n" ) ;
lua_getfield ( L , LUA_ENVIRONINDEX , "JellyThink" ) ;

return 1 ;
}


// 设置JellThink的值
static int Func2 ( lua_State * L ){
luaL_checkinteger ( L , 1 ) ;
// 设置到环境表中
lua_pushvalue ( L , 1 ) ;
lua_setfield ( L , LUA_ENVIRONINDEX , "JellyThink" ) ;

return 0 ;
}


当我在Lua中调用coroutine.resume时,我都只传递了一个参数,其它参数都没有;这里需要注意,如果我传值了,
就相当于给value赋值了。当我恢复thread1运行时,它是从Module.Func1()返回处继续执行,也就是对value赋值,
而这里赋予value的值实际上是传给resume的值。上面的代码中,我没有传值,如果传了,就无法验证我设置的10了。




2.Lua状态
每次调用luaL_newstate(或者lua_newstate)都会创建一个新的Lua状态。不同的Lua状态是各自完全独立的,
它们之间不共享任何数据。这个概念是不是很熟悉,是不是特别像Windows中的进程的概念。也就是说,在一个Lua
状态中发生的错误也不会影响其它的的Lua状态,windows的进程也是这样的。并且,Lua状态之间不能直接沟通,
必须写一些辅助代码来完成这点。
0
0
猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:19487次
    • 积分:771
    • 等级:
    • 排名:千里之外
    • 原创:59篇
    • 转载:9篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论