yield的使用

       在我刚开始学Python的时候,在看到生成器的时候,看到了一个叫”yield“的东东,当时没看懂后来用的也不是特别得多,(好啊,我承认这太傻了)。

        首先,我们看下面这段代码:  

  def foo():      print("start.....")      while True:        res=yield 4        print("res is %".formate(m))

       在这里,我们可以把yield理解为return(返回6,同时函数停止执行)。

 

       拥有yield的函数不会立即执行,会返回一个生成器。

g=foo()print(g)print(next(g)

                                       结果页面

 

       每次当你调用next()方法,函数才开始真正执行。函数执行到yield 4,返回一个4,随意打印的结果是这样。

 

        再次调用这个方法,打印的东西却发生了改变,这是为什么了。是因为next函数从上次next函数停止的地方开始重新执行。函数执行到res=yield 4的时候,只进行了yield,可以看成是return返回对象,然后函数就停止。这里并没有赋值,next()方法执行完毕。下次next()函数也同样从上次next()停止的地方再次开始。不会再打印"start"。

       这就是yield的使用原理,使用生成器,能够有效的使用资源。举个例子,在我们生成一个list的时候,如果我们不知道具体该设置多大的时候,我们一般会设置的稍微大一些,这样既会收到内存的限制也极有可能造成极大的资源浪费。

        我们就可以使用生成器的方式,这里使用廖雪峰老师教程上那个例子,斐波拉契数列。

        

def fib(max):    n, a, b = 0, 0, 1while n < max:yield b        a, b = b, a + b        n = n + 1return 'done'

           这里有一个注意点,

        

n,a,b=0,0,1

         这里可以看作一个元组

n,a,b=(0,0,1)a, b =(b, a + b)

      不存在b的值改变之后,会影响到a的值的情况。

      来看我们的代码,由于代码中有yield,函数不会立即执行,而是返回一个生成器,在调用的时候执行到yield b结束,在下次执行的时候,首先是函数的赋值,即斐波拉契数列的特性,后面的一个值就是前面两个数的和。由于while n<max且函数总是从上次函数结束的地方重新开始,这样就实现了我们所希望它实现的功能,可以获取到任何数量的连续斐波拉契数列。(可以使用for循环来迭代了)。

<think>我们正在讨论的是如何在C语言中正确使用`lua_yield`函数。根据引用[1]和引用[2],我们可以总结出以下关键点: 1. `lua_yield`必须在C函数的返回表达式中被调用,并且其返回值为-1。这是因为当注册的C函数(即`lua_CFunction`)返回值小于0时,Lua会将其解释为要求挂起(YIELD)当前协程的信号。而`lua_yield`的实现就是返回-1。 2. 在Lua中,当协程在C函数中挂起(通过`lua_yield`)后,恢复执行时,Lua会继续执行C函数中`lua_yield`之后的代码(如果有的话)?实际上,这里有一个重要的机制:当C函数中调用了`lua_yield`,该C函数会立即返回(返回值为-1),然后协程被挂起。当协程被恢复时,执行会从该C函数之后的下一条指令开始?但注意,实际上,C函数中在`lua_yield`之后的代码不会被执行,因为`lua_yield`已经使函数返回了。那么,恢复时如何继续执行呢? 实际上,根据引用[2]:当协程在C函数中挂起时,在恢复时,Lua会调用`luaD_poscall`,将`coroutine.resume`的参数作为`coroutine.yield`的返回值传递给挂起点。但是,这里有一个限制:`yield`不能跨C函数。也就是说,在C函数中挂起后,恢复时只能回到该C函数的挂起点,然后该C函数会继续执行(从挂起点之后开始执行?)但实际上,C函数在挂起时已经返回了,因此恢复时并不会回到C函数内部,而是回到调用该C函数的Lua代码中?这里需要澄清。 实际上,正确的流程是: - 一个C函数中调用`lua_yield`,该函数会返回-1(这个返回值是给Lua虚拟机看的,表示挂起)。 - 此时,协程被挂起,而该C函数并没有执行完,但它的执行状态(包括局部变量等)被保存下来。当协程被恢复时,执行会从该C函数中`lua_yield`之后继续执行?然而,根据引用[1]和引用[2]的说明,这并不支持。实际上,Lua的官方文档指出:在C函数中,你不能像在Lua函数中那样任意挂起,因为C函数需要做特殊设置(称为continuation)才能支持挂起和恢复。通常,我们使用`lua_yieldk`来支持在C函数中挂起并指定恢复后继续执行的函数(continuation函数)。 因此,在普通的C函数中,如果我们直接使用`lua_yield`,那么恢复后,我们无法回到C函数中继续执行。所以,通常的做法是:在C函数中,我们只使用一次`lua_yield`,并且在该函数返回之前(即在这个C函数中我们只挂起一次),然后当恢复时,整个C函数就结束了,控制权回到调用该C函数的Lua函数中。 但是,如果我们想要在C函数中多次挂起,并且恢复后继续执行C函数中的代码,我们需要使用`lua_yieldk`并提供一个continuation函数。不过,这个问题中我们关注的是`lua_yield`。 所以,总结一下:在普通的C函数中使用`lua_yield`,这个C函数必须是作为协程的入口点(即被`coroutine.create`包装的C函数)?或者是在一个已经被挂起的协程中调用的C函数?并且,该C函数调用`lua_yield`后,协程挂起,当恢复时,该C函数将不会继续执行,而是返回到调用它的Lua函数中(即C函数相当于在挂起点返回了,而恢复后,C函数已经不存在于调用栈中,控制权回到Lua)。 这个解释与引用[2]中的描述一致:yield不能跨C函数。也就是说,如果你在一个C函数中挂起,那么恢复时,你不能回到这个C函数中继续执行,而是会直接返回到调用这个C函数的Lua函数中(就好像这个C函数在挂起时已经返回了一样)。因此,C函数中挂起后,该C函数就退出了,恢复时执行的是调用该C函数的Lua代码。 因此,在C函数中使用`lua_yield`通常用于一次挂起,然后该C函数就结束(不会再继续执行)。当协程恢复时,C函数已经不存在于调用栈中。 3. 使用`lua_yield`时,需要传递参数,这些参数会成为`coroutine.yield`的返回值(即在Lua中调用`coroutine.resume`时,这些参数会作为`resume`的返回值)。同时,当协程恢复时,通过`coroutine.resume`传递的参数会成为`yield`的返回值(在C函数中,我们如何获取这些参数?实际上,在C函数中调用`lua_yield`后,函数就返回了,所以恢复时,我们无法在同一个C函数中获取到恢复时传入的参数。因此,通常我们不会在同一个C函数中再次执行,所以也就没有机会获取这些参数。这就是为什么我们需要使用`lua_yieldk`来指定一个continuation函数,在continuation函数中我们可以处理恢复时传入的参数。 鉴于以上分析,我们来看如何正确使用`lua_yield`: ### 使用场景1:在C函数中创建协程并挂起(一次挂起) 假设我们有一个C函数,它被注册到Lua中,然后在Lua中作为协程的入口函数。在这个C函数中,我们执行一些操作,然后挂起。 步骤: 1. 在C函数中,我们进行一些操作。 2. 然后,我们调用`lua_yield`,并传递一些值(这些值将作为`coroutine.yield`的返回值)。 3. 注意:在调用`lua_yield`之前,我们需要将需要返回给`yield`的值压入栈中。 示例C函数: ```c int my_cfunc(lua_State *L) { // 做一些事情... lua_pushstring(L, "This is from C before yield"); // 我们想要挂起,并传递一个字符串和一个整数 lua_pushstring(L, "yield value1"); lua_pushinteger(L, 100); return lua_yield(L, 2); // 返回2个值,并挂起 } ``` 在Lua中: ```lua co = coroutine.create(my_cfunc) status, val1, val2 = coroutine.resume(co) print(val1, val2) -- 输出: yield value1 100 ``` 注意:当我们在C函数中调用`lua_yield`时,我们返回了两个值,然后协程挂起。在Lua中,通过`coroutine.resume`得到这两个值。 但是,当恢复这个协程时,由于C函数已经结束(它只是返回了`lua_yield`),所以恢复后协程的状态变为dead(因为C函数已经执行完毕,没有后续代码)。所以,实际上这个协程只能挂起一次,然后再次恢复时,协程就结束了。 ### 使用场景2:在C函数中挂起后,恢复时如何处理? 如果我们希望协程在恢复时执行一些操作,那么我们不能在同一个C函数中实现,因为该C函数已经结束。因此,我们需要使用`lua_yieldk`,它允许我们指定一个continuation函数。当协程恢复时,Lua会调用这个continuation函数。 由于问题中只提到`lua_yield`,我们这里不展开`lua_yieldk`。 ### 另一个重要点:如何设置协程的入口? 根据引用[4]中的示例,创建协程并注册C函数: ```c lua_State* newL = CreateCoroutine(L,"CorTest"); lua_register(newL,"Stop",Stop); int re = lua_resume(newL, 0); ``` 这里,`CreateCoroutine`可能是自定义函数,用于创建一个新的线程(协程)并设置入口函数(这里可能是Lua函数"CorTest")。然后,注册了一个C函数"Stop"到该协程中。接着,恢复协程。 ### 结论:lua_yield使用方法 1. 只能在C函数(`lua_CFunction`)中使用`lua_yield`。 2. 在C函数中,`lua_yield`必须在return语句中调用,且返回值为`lua_yield(L, n)`,其中n表示要返回给`resume`的值的个数。 3. 在调用`lua_yield`之前,需要将n个值压入栈中。 4. 注意:在同一个C函数中,一旦调用`lua_yield`,该函数就会挂起,并且当恢复时,该C函数不会继续执行(即函数已经退出)。因此,该C函数只能挂起一次。 5. 如果需要在C函数中多次挂起,或者需要在恢复后继续执行C函数的代码,必须使用`lua_yieldk`并提供continuation函数。 ### 示例代码 #### C端代码 ```c #include <lua.h> #include <lauxlib.h> #include <lualib.h> // 示例C函数,作为协程的入口 static int my_yielding_function(lua_State *L) { // 第一次,我们直接挂起,并返回两个值 lua_pushstring(L, "Hello"); lua_pushinteger(L, 42); return lua_yield(L, 2); // 挂起,并返回两个值 } int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L); // 注册我们的C函数 lua_register(L, "my_yielding_function", my_yielding_function); // 创建协程 lua_State *co = lua_newthread(L); // 将协程的入口函数压栈 lua_getglobal(L, "my_yielding_function"); // 将函数移到协程co的栈中 lua_xmove(L, co, 1); // 将函数从L移动到co栈中(此时co栈顶是my_yielding_function) // 第一次恢复协程 int status = lua_resume(co, L, 0); if (status == LUA_YIELD) { // 协程挂起,打印yield返回的值 int n = lua_gettop(co); // 获取yield返回值的个数 for (int i = 1; i <= n; i++) { if (lua_isstring(co, i)) { printf("Yielded string: %s\n", lua_tostring(co, i)); } else if (lua_isinteger(co, i)) { printf("Yielded integer: %lld\n", lua_tointeger(co, i)); } } lua_pop(co, n); // 清除yield返回的值 } // 再次恢复协程(此时协程应该已经结束,因为my_yielding_function已经执行完毕) status = lua_resume(co, L, 0); if (status == LUA_OK) { printf("Coroutine finished\n"); } else if (status == LUA_YIELD) { // 不应该发生,因为只yield了一次 } else { printf("Error in coroutine: %s\n", lua_tostring(co, -1)); } lua_close(L); return 0; } ``` 运行结果: ``` Yielded string: Hello Yielded integer: 42 Coroutine finished ``` 注意:这个协程只挂起一次,然后恢复后协程就结束了(因为C函数`my_yielding_function`在挂起后就已经返回了,所以恢复时,该协程没有其他可执行的代码,因此状态变为结束)。 ### 相关问题 1. 如何在C函数中多次挂起协程? 2. `lua_yield`和`lua_yieldk`有什么区别? 3. 协程在C函数中挂起后,如何传递恢复参数? 4. 如何检查协程的状态? 5. 在C函数中挂起协程时,如何保存和恢复局部状态? </think> ### `lua_yield` 函数使用方法详解 `lua_yield` 是 Lua C API 中用于在 C 函数内部挂起协程的关键函数,它必须遵循特定的调用规则和使用模式。 #### 一、基本使用规则 1. **必须作为 C 函数的返回值** 只能在注册为 Lua 函数的 C 函数中调用,且必须放在 `return` 语句中: ```c int my_cfunc(lua_State *L) { // ... 前置逻辑 return lua_yield(L, n); // n = 返回值的数量 } ``` 2. **参数说明** ```c int lua_yield(lua_State *L, int nresults); ``` - `L`:当前协程的 Lua 状态机 - `nresults`:传递给 `coroutine.resume` 的返回值数量 3. **调用前必须压入返回值** 在调用前需将返回值压入栈: ```c lua_pushstring(L, "yield data"); lua_pushinteger(L, 100); return lua_yield(L, 2); // 返回两个值 ``` #### 二、完整使用示例 ##### C 端代码 ```c #include <lua.h> #include <lauxlib.h> #include <lualib.h> // 可挂起的C函数 static int yieldable_func(lua_State *L) { printf("C函数执行中...\n"); // 压入要返回的值 lua_pushstring(L, "来自C的yield数据"); lua_pushinteger(L, 42); // 挂起协程(返回2个值) return lua_yield(L, 2); } int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L); // 注册C函数到Lua lua_register(L, "cyield", yieldable_func); // 创建协程 lua_State *co = lua_newthread(L); luaL_dostring(co, "function coro_main()\n" " print('Lua协程开始')\n" " local s, n = cyield()\n" // 调用C函数并挂起 " print('收到C传递:', s, n)\n" " return '协程结束'\n" "end" ); // 获取协程入口函数 lua_getglobal(co, "coro_main"); // 第一次恢复协程 int status = lua_resume(co, L, 0); if (status == LUA_YIELD) { printf("C端收到挂起值: %s, %d\n", lua_tostring(co, -2), lua_tointeger(co, -1)); lua_pop(co, 2); // 清理yield返回值 } // 第二次恢复协程 status = lua_resume(co, L, 0); if (status == LUA_OK) { printf("协程结果: %s\n", lua_tostring(co, -1)); } lua_close(L); return 0; } ``` ##### 执行流程 ```mermaid sequenceDiagram participant C as C程序 participant Co as Lua协程 participant CF as C函数 C->>Co: lua_resume() Co->>CF: 调用cyield() CF->>Co: lua_yield(2值) Co->>C: 返回LUA_YIELD状态 C->>Co: 再次lua_resume() Co->>C: 返回最终结果 ``` #### 三、关键注意事项 1. **禁止跨 C 函数挂起** 如引用[2]指出,yield 不能跨 C 函数边界。挂起必须在 **同一个 C 函数内恢复**,不能在嵌套调用中挂起[^2]。 2. **返回值处理机制** - `lua_yield` 的返回值会被传递给 `coroutine.resume` - 恢复时通过 `lua_resume` 的返回值判断状态: ```c int status = lua_resume(co, L, nargs); if (status == LUA_YIELD) { // 处理yield返回值 } ``` 3. **与 Lua 端的交互** Lua 端通过 `coroutine.yield()` 接收 C 端传递的值: ```lua local data1, data2 = cyield() -- 接收C函数的yield返回值 ``` #### 四、典型错误场景 **错误示例**(跨函数挂起): ```c static int nested_func(lua_State *L) { return lua_yield(L, 0); // 错误!不能在嵌套函数中yield } static int main_func(lua_State *L) { nested_func(L); // 调用嵌套函数 return 0; } ``` 此调用会导致未定义行为,可能引发 Lua 虚拟机崩溃。 #### 五、替代方案(需要多次挂起) 如需在 C 函数内多次挂起,应使用 `lua_yieldk`: ```c static int continuation(lua_State *L, int status, lua_KContext ctx) { // 恢复后的处理逻辑 } static int yieldable_func(lua_State *L) { // ... return lua_yieldk(L, 0, ctx, continuation); } ``` ### 相关问题 1. `lua_yield` 和 `lua_yieldk` 的主要区别是什么? 2. 如何在挂起的协程中传递复杂数据结构? 3. 协程挂起时如何保存 C 函数的局部状态? 4. Lua 协程和 C++20 协程如何交互? 5. 如何调试跨 C/Lua 的协程调用栈? [^1]: `lua_yield` 必须在 return 表达式中调用,返回 -1 触发挂起机制 : yield 不能跨 C 函数边界,恢复时参数作为 yield 返回值传递
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值