- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
在不久前的blog post中,Scott Vokes使用C函数setjmp
和longjmp
描述了与lua实现协程相关的技术问题:
The main limitation of Lua coroutines is that, since they are implemented with setjmp(3) and longjmp(3), you cannot use them to call from Lua into C code that calls back into Lua that calls back into C, because the nested longjmp will clobber the C function’s stack frames. (This is detected at runtime, rather than failing silently.)
I haven’t found this to be a problem in practice, and I’m not aware of any way to fix it without damaging Lua’s portability, one of my favorite things about Lua — it will run on literally anything with an ANSI C compiler and a modest amount of space. Using Lua means I can travel light. :)
setjmp
和
longjmp
的工作,但是我在某个时候读了一下,并意识到我并不真正了解它。为了解决这个问题,我尝试根据描述创建一个我认为应该引起问题的程序,它似乎可以正常工作。
extern "C" {
#include <lauxlib.h>
#include <lua.h>
}
#include <cassert>
#include <iostream>
#define CODE(C) \
case C: { \
std::cout << "When returning to " << where << " got code '" #C "'" << std::endl; \
break; \
}
void handle_resume_code(int code, const char * where) {
switch (code) {
CODE(LUA_OK)
CODE(LUA_YIELD)
CODE(LUA_ERRRUN)
CODE(LUA_ERRMEM)
CODE(LUA_ERRERR)
default:
std::cout << "An unknown error code in " << where << std::endl;
}
}
int trivial(lua_State *, int, lua_KContext) {
std::cout << "Called continuation function" << std::endl;
return 0;
}
int f(lua_State * L) {
std::cout << "Called function 'f'" << std::endl;
return 0;
}
int g(lua_State * L) {
std::cout << "Called function 'g'" << std::endl;
lua_State * T = lua_newthread(L);
lua_getglobal(T, "f");
handle_resume_code(lua_resume(T, L, 0), __func__);
return lua_yieldk(L, 0, 0, trivial);
}
int h(lua_State * L) {
std::cout << "Called function 'h'" << std::endl;
lua_State * T = lua_newthread(L);
lua_getglobal(T, "g");
handle_resume_code(lua_resume(T, L, 0), __func__);
return lua_yieldk(L, 0, 0, trivial);
}
int main () {
std::cout << "Starting:" << std::endl;
lua_State * L = luaL_newstate();
// init
{
lua_pushcfunction(L, f);
lua_setglobal(L, "f");
lua_pushcfunction(L, g);
lua_setglobal(L, "g");
lua_pushcfunction(L, h);
lua_setglobal(L, "h");
}
assert(lua_gettop(L) == 0);
// Some action
{
lua_State * T = lua_newthread(L);
lua_getglobal(T, "h");
handle_resume_code(lua_resume(T, nullptr, 0), __func__);
}
lua_close(L);
std::cout << "Bye! :-)" << std::endl;
}
Starting:
Called function 'h'
Called function 'g'
Called function 'f'
When returning to g got code 'LUA_OK'
When returning to h got code 'LUA_YIELD'
When returning to main got code 'LUA_YIELD'
Bye! :-)
longjmp
丢掉了一部分C堆栈,如果您希望以后再恢复堆栈,那就太糟糕了。
lua_yieldk
的
this to say:
Yields a coroutine.
This function should only be called as the return expression of a C function, as follows:
return lua_yieldk (L, n, i, k);
lua_yield
:
Yields a coroutine.
This function should only be called as the return expression of a C function, as follows:
return lua_yieldk (L, n, i, k);
return
无关紧要?如果lua_yieldk
将调用longjmp
,那么lua_yieldk
将永远不会返回,因此,如果我返回则没关系吗?所以那不可能是正在发生的事,对吧? lua_yieldk
只是在lua状态下做一个记号,说明当前C api调用已声明要屈服,然后在最终返回时,lua会弄清楚接下来会发生什么。然后,这解决了保存C堆栈帧的问题,不是吗?自从我们正常返回lua之后,那些堆栈帧无论如何都已经过期-因此@Nicol Bolas图片中描述的复杂性会绕开吗?其次,至少在5.2中,语义从来都不是我们应该还原C堆栈帧的地方,似乎-lua_yieldk
恢复为延续函数,而不是lua_yieldk
调用者,并且lua_yield
显然恢复为当前api调用的调用者,而不是lua_yield
调用者本身。 If I consistently use
lua_yieldk
in the formreturn lua_yieldk(...)
specified in the docs, returning from alua_CFunction
that was passed to lua, is it still possible to trigger theattempt to yield across a C-call boundary
error?
attempt to yield across a C-call boundary
错误的样子。我的想法是,可能存在与
setjmp
和
longjmp
扔掉以后需要的堆栈框架相关的问题,但是我想看到一些我可以指向的真正的lua/lua c api代码,并说“例如,不要那个”,这令人惊讶地难以捉摸。
lua_resume
。相反,用户在线程堆栈上使用了
dofile
,它在加载后在其中执行了该函数,而不是继续执行该函数。所以实际上是
yield outside of a coroutine
iiuc,当我对此进行修补时,他的代码可以很好地使用lua 5.3中的
lua_yield
和
lua_yieldk
。
#include <cassert>
#include <cstdio>
extern "C" {
#include "lua.h"
#include "lauxlib.h"
}
//#define USE_YIELDK
bool running = true;
int lua_print(lua_State * L) {
if (lua_gettop(L)) {
printf("lua: %s\n", lua_tostring(L, -1));
}
return 0;
}
int lua_finish(lua_State *L) {
running = false;
printf("%s called\n", __func__);
return 0;
}
int trivial(lua_State *, int, lua_KContext) {
printf("%s called\n", __func__);
return 0;
}
int lua_sleep(lua_State *L) {
printf("%s called\n", __func__);
#ifdef USE_YIELDK
printf("Calling lua_yieldk\n");
return lua_yieldk(L, 0, 0, trivial);
#else
printf("Calling lua_yield\n");
return lua_yield(L, 0);
#endif
}
const char * loop_lua =
"print(\"loop.lua\")\n"
"\n"
"local i = 0\n"
"while true do\n"
" print(\"lua_loop iteration\")\n"
" sleep()\n"
"\n"
" i = i + 1\n"
" if i == 4 then\n"
" break\n"
" end\n"
"end\n"
"\n"
"finish()\n";
int main() {
lua_State * L = luaL_newstate();
lua_pushcfunction(L, lua_print);
lua_setglobal(L, "print");
lua_pushcfunction(L, lua_sleep);
lua_setglobal(L, "sleep");
lua_pushcfunction(L, lua_finish);
lua_setglobal(L, "finish");
lua_State* cL = lua_newthread(L);
assert(LUA_OK == luaL_loadstring(cL, loop_lua));
/*{
int result = lua_pcall(cL, 0, 0, 0);
if (result != LUA_OK) {
printf("%s error: %s\n", result == LUA_ERRRUN ? "Runtime" : "Unknown", lua_tostring(cL, -1));
return 1;
}
}*/
// ^ This pcall (predictably) causes an error -- if we try to execute the
// script, it is going to call things that attempt to yield, but we did not
// start the script with lua_resume, we started it with pcall, so it's not
// okay to yield.
// The reported error is "attempt to yield across a C-call boundary", but what
// is really happening is just "yield from outside a coroutine" I suppose...
while (running) {
int status;
printf("Waking up coroutine\n");
status = lua_resume(cL, L, 0);
if (status == LUA_YIELD) {
printf("coroutine yielding\n");
} else {
running = false; // you can't try to resume if it didn't yield
if (status == LUA_ERRRUN) {
printf("Runtime error: %s\n", lua_isstring(cL, -1) ? lua_tostring(cL, -1) : "(unknown)" );
lua_pop(cL, -1);
break;
} else if (status == LUA_OK) {
printf("coroutine finished\n");
} else {
printf("Unknown error\n");
}
}
}
lua_close(L);
printf("Bye! :-)\n");
return 0;
}
USE_YIELDK
被注释掉后的输出:
Waking up coroutine
lua: loop.lua
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua_finish called
coroutine finished
Bye! :-)
USE_YIELDK
时的输出:
Waking up coroutine
lua: loop.lua
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua_finish called
coroutine finished
Bye! :-)
最佳答案
想一想,当协程执行yield
时会发生什么。它停止执行,并且处理返回到该协程中称为resume
的任何人,对吗?
好吧,假设您有以下代码:
function top()
coroutine.yield()
end
function middle()
top()
end
function bottom()
middle()
end
local co = coroutine.create(bottom);
coroutine.resume(co);
yield
的时刻,Lua堆栈如下所示:
-- top
-- middle
-- bottom
-- yield point
yield
时,将保留作为协程的一部分的Lua调用堆栈。当您执行
resume
时,将再次执行保留的调用堆栈,从之前中断的位置开始。
middle
实际上不是Lua函数。相反,它是一个C函数,并且该C函数调用Lua函数
top
。因此,从概念上讲,您的堆栈如下所示:
-- Lua - top
-- C - middle
-- Lua - bottom
-- Lua - yield point
lua_State
定义。还有C的堆栈。在即将调用
yield
时,Lua的内部堆栈看起来像这样:
-- top
-- Some C stuff
-- bottom
-- yield point
-- arbitrary Lua interpreter stuff
-- middle
-- arbitrary Lua interpreter stuff
-- setjmp
yield
时,它将调用
longjmp
。该函数基于C堆栈的行为。即,它将返回到
setjmp
所在的位置。
longjmp
和
setjmp
?之间的所有内容。没了凯普特。永远失去了。
yield
ed协程怎么办?
lua_callk
或
lua_pcallk
调用可能有用的Lua函数。这些调用函数带有一个附加参数:“继续”函数。
lua_*callk
函数将永远不会真正返回(因为您的C堆栈将被销毁)。相反,它将调用您在
lua_*callk
函数中提供的延续函数。顾名思义,延续功能的工作是从上一个功能中断的地方继续。
lua_*callk
)被删除,并返回该函数的值被压入堆栈。除此之外,堆栈都是一样的。
lua_yieldk
。这使您的C函数可以返回Lua,以便在协程恢复后调用提供的延续函数。
关于c++ - Lua协程-setjmp longjmp破坏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34303507/
在我的设置中,我试图有一个界面 Table继承自 Map (因为它主要用作 map 的包装器)。两个类继承自 Table - 本地和全局。全局的将有一个可变的映射,而本地的将有一个只有本地条目的映射。
Rust Nomicon 有 an entire section on variance除了关于 Box 的这一小节,我或多或少地理解了这一点和 Vec在 T 上(共同)变体. Box and Vec
我是一名优秀的程序员,十分优秀!