luci的context
luci的context
最近有个需求是在luci的交互界面上增加本地ipk的安装功能,因此要去研究下luci。
luci的httpdispatch函数解析了路径信息后调用dispatch来处理请求。代码如下:
local stat, err = util.coxpcall(function()
dispatch(context.request)
end, error500)
luci的主体部分正是dispatch,dispatch中有个很重要的表context。很多重要的处理都跟他有关。所以先搞清楚这个数据的话,应该对理解dispatch这个函数有很大的帮组。
1、 context
context = util.threadlocal()
function threadlocal(tbl)
return setmetatable(tbl or {}, tl_meta)
end
从上面的代码可以得出的结论是context是一个表,而且它的元表是tl_meta。
2、 tl_meta
local tl_meta = {
__mode = "k",
__index = function(self, key)
local t = rawget(self, coxpt[coroutine.running()]
or coroutine.running() or 0)
return t and t[key]
end,
__newindex = function(self, key, value)
local c = coxpt[coroutine.running()] or coroutine.running() or 0
local r = rawget(self, c)
if not r then
rawset(self, c, { [key] = value })
else
r[key] = value
end
end
}
__mode="k"表示tl_meta这个表的弱引用类型是是key的弱引用。这个字段涉及lua的垃圾回收机制,垃圾回收应该对运行的逻辑关系没什么关系,暂且不管。
下面的__index用于查询,__newindex用于更新。也就是说,如果对一个表不存在的元素进行访问,就会执行__index里面的代码。如果对一个表不存在的元素进行赋值的时候,就会执行__newindex里面的代码。
2.1、__newindex
因为对于一个新定义的表来说,首先操作的赋值操作,所以先分析__newindex。
local c = coxpt[coroutine.running()] or coroutine.running() or 0
coroutine.running()在lua5.2的手册里的定义是:返回当前运行的协程及一个布尔值,如果布尔值为 true 则表示当前运行的协程为主协
程。
所以这个c的值就是当前运行的协程号。实际代码中打印的结果是:thread: 0x9bd4678,thread类型的数据。
local r = rawget(self, c)
if not r then
rawset(self, c, { [key] = value })
else
r[key] = value
end
在第一次运行的时候,所以在self这个表中是nil,所以下面的if语句执行的就是:
rawset(self, c, { [key] = value })
把self的c这个index下的值设置为一个表,表的内容就是调用者所给的名/值对。
然后之后调用的时候,如果是同一个协程的话,就直接修改值,也就是if语句中的else部分的代码。
这么做的用意是给每个协程都提供一个位置,从而让每个协程都操作自己的空间,而不会影响别的协程的空间。
2.2、__index
与__newindex类似的,先获取每个协程自己的空间的key-value,没有的话直接返回key-value
2.3 coxpt[coroutine.running()]
前面的内容还没提到coxpt[coroutine.running()]的作用。在luci代码中对coxpt这个表的赋值操作只有一处:
function coxpcall(f, err, ...)
local res, co = oldpcall(coroutine.create, f)
if not res then
local params = {...}
local newf = function() return f(unpack(params)) end
co = coroutine.create(newf)
end
local c = coroutine.running()
coxpt[co] = coxpt[c] or c or 0
return performResume(err, co, ...)
end
oldpcall在程序中被赋值成pcall。lua5.2对pcall的解释是:
pcall
在保护模式下用指定的参数调用函数 f。 这意味着在 f 中发生的任何错误都不会被传递 ,
相反,pcall 捕获错误并返回一个状态码。它的第一个返回值是一个状态码(一个布尔值) ,
如果为 true 则表示没有错误发生,调用成功。这种情况下,pcall 还会返回 f 函数的所有返
回值,这些返回值在状态码之后被返回。如果发生了错误,pcall 返回 e false 及错识消息
所以res,co的值分别对应的是pcall的状态码和coroutine.create()所返回的一个“thread”的对象。当oldpcall调用出错的情况下,函数进入if语句重新创建一个协程。所以这个coxpcall调用的一个目的是增加程序的健壮性。
给返回当前的协程号返回给c,这里说明一点的是如果是同一个协程那么coroutine.running()和coroutine.create()返回的协程号是一样的(经过程序验证)。
coxpt[co] = coxpt[c] or c or 0 这条表达式就把协程号赋值给coxpt[co]。再回到前面的计算索引的表达式:
local c = coxpt[coroutine.running()] or coroutine.running() or 0
就可以知道只要在对contex操作之前先执行2.3的这个函数调用就能够直接查表获得当前的协程号了,coxpt的作用在于提高程序的运行效率。
最后return performResume(err, co, ...) 这个函数调用的意义是进行错误的回溯及保证co这个协程处理完成进入状态“dead"
总结:
context是一个表,它的特殊之处在于它可以为不同的协程分配不同的索引,来实现协程之间的访问内存隔离。此外为了提高效率,context还有一个用来存放索引号的coxpt的表。
下一篇: 【机器学习基础】KNN实现手写数字识别