LUA 闭包(Closure)分析

1. 闭包名词的由来,为什么要叫闭包?
  这个术语在编程领域的最早出现,大致可以追溯到Landin于1964年发表的The mechanical evaluation of expressions。(https://www.zhihu.com/question/422554486)

  而真正有详细解析的是Moses写的一篇文章:A useful metaphor for the difference between FUNCTION and QUOTE in LISP is to think of QUOTE as a porous or an open covering of the function since free variables escape to the current environment. FUNCTION acts as a closed or nonporous covering (hence the term "closure" used by Landin). Thus we talk of "open" Lambda expressions (functions in LISP are usually Lambda expressions) and "closed" Lambda expressions. 

意思大概是因为FUNCTION acts as a closed or nonporous covering 函数充当封闭或无孔覆盖,所以取名"闭包"。

2. 闭包的功能?
    闭包的功能是让一个函数的变量逃不出去,什么意思?意思是当一个函数被当做成是一个返回值时,这个函数里所有引用的变量都不会丢失,不会因为离开了一个变量离开了作用域而导致创建或者使用这个函数时,其变量值会发生变化。

 

3. LUA中如何实现闭包?
    LUA中两种闭包,一种是C闭包,一种是lua闭包,以下是lua闭包的原型。

/*
** Closures
*/

#define ClosureHeader \
    CommonHeader; lu_byte nupvalues; GCObject *gclist

typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;
  TValue upvalue[1];  /* list of upvalues */
} CClosure;

typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;
  UpVal *upvals[1];  /* list of upvalues */
} LClosure;

typedef union Closure {
  CClosure c;
  LClosure l;
} Closure;

 可以看出,闭包在lua中都是由函数原型加上upvalue(upvalue是一个函数所使用到的外部局部变量)数组实现的。

 

4. LUA中实现闭包所需要解决的问题。

    LUA在不同的情境中需要不同处理。

  • 1
function Closure_t(x)
    function inner_fun (y)
        return x + y
    end
    return inner_fun
end
local fun_closure = Closure_t(10)

       这里inner_fun里使用到的x,在执行 local fun_closure = Closure_t(10) 完成后,其x已经离开了作用域,在传统函数中,应该被回收掉,但是我们希望正确运行,即x的值保存为在创建闭包时的10,这时就需要使用到lua的upvalue。

  • 2
function add3 (x) -- f1
  return function (y) -- f2
    return function (z) return x + y + z end -- f3
  end
end

       内层函数f3需要 x与y和z三个变量。在创建闭包时,y在当前执行的函数f2的活动记录中,而x在闭包创建时可能已经消失了,为了解决这个问题,编译器将x放入了f2的upvalue数组中。

       这里1)和2)中涉及到了upvalue结构体中的一个参数u,如下:

/*
** Upvalues for Lua closures
*/
struct UpVal {
  TValue *v;  /* points to stack or to its own value */
  lu_mem refcount;  /* reference counter */
  union {
    struct {  /* (when open) */
      UpVal *next;  /* linked list */
      int touched;  /* mark to avoid cycles with dead threads */
    } open;
    TValue value;  /* the value (when closed) */
  } u;
};

       参数u有两个值,用来区别于当前使用到的upval是在f2中还是在f1中,如果在f2中,则表示其值(y)仍在栈中,这时它的值是open的,另外一个情况是f1中的x,他的值是关闭的,表示不在栈中,在其他闭包的upval数组中。

       一个upvalue有两种状态:open和closed。当一个upvalue被创建时,它是open的,并且它的指针指向Lua栈中对应的变量。当Lua关闭了一个upvalue(即当代码执行到vmcase(OP_TAILCALL),vmcase(OP_RETURN),vmcase(OP_JMP)等语句离开了作用域时),upvalue指向的值被复制到upvalue结构内部,并且指针也相应的从指向栈中的upvalue调整成指向结构内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void luaF_close (lua_State *L, StkId level) {
  UpVal *uv;
  while (L->openupval != NULL && (uv = L->openupval)->v >= level) {
    lua_assert(upisopen(uv));
    L->openupval = uv->u.open.next;  /* remove from 'open' list */
    if (uv->refcount == 0)  /* no references? */
      luaM_free(L, uv);  /* free upvalue */
    else {
      setobj(L, &uv->u.value, uv->v);  /* move value to upvalue slot */
      uv->v = &uv->u.value;  /* now current value lives here */
      luaC_upvalbarrier(L, uv);
    }
  }
}

  

 

 

 

  • 3
function Closure(x)
    local function print_fun()
        print(x)
    end
    local function add_fun(y)
        x = x + y
    end
    return print_fun, add_fun
end
local print_fun, add_fun = Closure(10)
print_fun()
add_fun(10)
print_fun()

       此时需要处理的问题是,当同时创建了两个闭包,每个闭包中都有自己独立的upval数组,这里就出现了共享的问题。
       为了解决这个问题,解释器必须确保每个变量最多只有一个upval指向它。在创建闭包时,lua解释器维护了一个保护栈中所有open value的链表,当解释器需要一个变量的upvalue时,它首先遍历这个链表:如果找到变量对应的upvalue,则复用它,因此确保了共享;否则创建一个新的upvalue并将其链入链表中正确的位置。

 

openvalue 保存在lua_State结构体中:

/*
** 'per thread' state
*/
struct lua_State {
  ...
  CallInfo *ci;  /* call info for current function */
  const Instruction *oldpc;  /* last pc traced */
  StkId stack_last;  /* last free slot in the stack */
  StkId stack;  /* stack base */
  UpVal *openupval;  /* list of open upvalues in this stack */
  ...
}  

当要查找一个upvalue时,可参考luaF_findupval 函数:

点击查看代码

UpVal *luaF_findupval (lua_State *L, StkId level) {
  UpVal **pp = &L->openupval;
  UpVal *p;
  UpVal *uv;
  lua_assert(isintwups(L) || L->openupval == NULL);
  while (*pp != NULL && (p = *pp)->v >= level) {
    lua_assert(upisopen(p));
    if (p->v == level)  /* found a corresponding upvalue? */
      return p;  /* return it */
    pp = &p->u.open.next;
  }
  /* not found: create a new upvalue */
  uv = luaM_new(L, UpVal);
  uv->refcount = 0;
  uv->u.open.next = *pp;  /* link it to list of open upvalues */
  uv->u.open.touched = 1;
  *pp = uv;
  uv->v = level;  /* current value lives in the stack */
  if (!isintwups(L)) {  /* thread not in list of threads with upvalues? */
    L->twups = G(L)->twups;  /* link it to the list */
    G(L)->twups = L;
  }
  return uv;
}

 

5.闭包只是一个功能,lua实现闭包只是实现功能的一个手段。

posted @   小乐虎  阅读(829)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示