【问题标题】:What are Lua coroutines even for? Why doesn't this code work as I expect it?Lua 协程有什么用?为什么这段代码没有像我预期的那样工作?
【发布时间】:2011-07-04 22:38:40
【问题描述】:

我无法理解这段代码...我期待类似于线程的东西,我会得到一个随机“nooo”和“yaaaay”的输出,因为它们都以异步方式进行打印,但是相反,我发现主线程似乎在第一次调用 coroutine.resume() 时被阻塞,因此阻止下一个启动,直到第一个产生。

如果这是预期的操作协程,它们有什么用,我将如何实现我希望的目标?是不是得自己实现调度器才能让这些协程异步操作?,因为看起来很乱,还不如用函数!

co1 = coroutine.create(function ()
        local i = 1
        while i < 200 do
                print("nooo")
                i = i + 1
        end
        coroutine.yield()
end)

co2 = coroutine.create(function ()
        local i = 1
        while i < 200 do
                print("yaaaay")
                i = i + 1
        end
        coroutine.yield()
end)

coroutine.resume(co1)
coroutine.resume(co2)

【问题讨论】:

  • 协程不是线程。它们类似于线程,因为它们是独立的执行环境,但您的程序是调度程序,它们不是抢占式的。我会添加一个完整的答案,但是我的 Lua 生锈了,所以我无法提供任何代码示例...

标签: multithreading lua coroutine


【解决方案1】:

协程不是线程。

协程就像从未主动调度过的线程。所以是的,你有点正确,你必须编写自己的调度程序才能让两个协程同时运行。

但是,当涉及到协程时,您会错过更大的图景。查看维基百科的list of coroutine uses。下面是一个具体示例,可能会引导您朝着正确的方向前进。

    -- level script
    -- a volcano erupts every 2 minutes
    function level_with_volcano( interface )
    
        while true do
            wait(seconds(5))
            start_eruption_volcano()
            wait(frames(10))
            s = play("rumble_sound")
            wait( end_of(s) )
            start_camera_shake()
       
            -- more stuff

            wait(minutes(2))
        end
    
    end

上面的脚本可以编写为使用 switch 语句和一些巧妙的状态变量迭代运行。但是当写成协程时会更清楚。上面的脚本可能是一个线程,但你真的需要一个内核线程来处理这个简单的代码吗?一个繁忙的游戏关卡可能会运行 100 个这样的协程,而不会影响性能。但是,如果其中每一个都是线程,那么您可能在性能开始受到影响之前 20-30 就可以逃脱。

协程是为了让我可以编写将状态存储在堆栈上的代码,以便我可以暂时停止运行它(wait 函数)并在我停止的地方重新启动它。

【讨论】:

  • 谢谢,这很好地回答了我的问题!
  • @Victor,是的,你自己实现它。我添加了一些额外的东西,你可以看到 Lua 协程对于脚本编写有多好。主程序(在 C 或 Lua 中)创建一个协程来运行 level_with_volcanoend_offramessecondsminutes 函数返回 wait 可以理解的特殊类型。 wait 函数使用该特殊类型来决定协程需要休眠多长时间并将该值提供给主程序。主程序继续其业务(绘图、更新等),经过足够的时间后,它恢复 level_with_volcano 协程。
  • @deft_code 您能否提供这个特定用例的简化 lua 代码?我花了三个小时试图理解协程的概念,现在我觉得很愚蠢,因为我仍然无法实现你的示例。
  • 我看到这个已经一年多没有被触及了,但我还想看看 wait 函数的代码实现。
  • @Moop: TL;DR: Nuh Uh!基本问题是我没有将“Lua 线程”称为线程。所以你的评论是真的,它们在技术上是 Lua 线程。我可以回答,它们不是线程,它们是协程。 Yea Huh,Nuh Uh,Yea Huh,Nuh Uh……我们都对,但是使用“线程”具有两种不同的含义。现在来一个不太明智的答案:coroutine.createluaB_cocreate 实现,它在堆栈上返回一个lua_Statetype 函数在收到lua_State 时返回"thread"
【解决方案2】:
co1 = coroutine.create(
    function()
        for i = 1, 100 do
            print("co1_"..i)
            coroutine.yield(co2)
        end
    end
)

co2 = coroutine.create(
    function()
        for i = 1, 100 do
            print("co2_"..i)
            coroutine.yield(co1)
        end
    end
)

for i = 1, 100 do
    coroutine.resume(co1)
    coroutine.resume(co2)
end

【讨论】:

    【解决方案3】:

    由于有许多 cmets 询问如何实现 wait 函数以使 deft_code 的示例工作,我决定编写一个可能的实现。总体思路是,我们有一个带有协程列表的调度程序,调度程序决定在协程通过wait 调用放弃控制权后何时将控制权返回给协程。这是可取的,因为它使异步代码可读且易于推理。

    这只是协程的一种可能用途,它们是一种更通用的抽象工具,可用于许多不同的目的(例如编写迭代器和生成器,编写有状态的流处理对象(例如,解析器中的多个阶段) ,实现异常和延续等)。

    首先:调度器定义:

    local function make_scheduler()
        local script_container = {}
        return {
            continue_script = function(frame, script_thread)
                if script_container[frame] == nil then
                    script_container[frame] = {}
                end
                table.insert(script_container[frame],script_thread)
            end,
            run = function(frame_number, game_control)
                if script_container[frame_number] ~= nil then
                    local i = 1
                    --recheck length every time, to allow coroutine to resume on
                    --the same frame
                    local scripts = script_container[frame_number]
                    while i <= #scripts do
                        local success, msg =
                            coroutine.resume(scripts[i], game_control)
                        if not success then error(msg) end
                        i = i + 1
                    end
                end
            end
        }
    end
    

    现在,初始化世界:

    local fps = 60
    local frame_number = 1
    local scheduler = make_scheduler()
    
    scheduler.continue_script(frame_number, coroutine.create(function(game_control)
        while true do
            --instead of passing game_control as a parameter, we could
            --have equivalently put these values in _ENV.
            game_control.wait(game_control.seconds(5))
            game_control.start_eruption_volcano()
            game_control.wait(game_control.frames(10))
            s = game_control.play("rumble_sound")
            game_control.wait( game_control.end_of(s) )
            game_control.start_camera_shake()
    
            -- more stuff
    
            game_control.wait(game_control.minutes(2))
        end
    end))
    

    游戏的(虚拟)界面:

    local game_control = {
        seconds = function(num)
            return math.floor(num*fps)
        end,
        minutes = function(num)
            return math.floor(num*fps*60)
        end,
        frames = function(num) return num end,
        end_of = function(sound)
            return sound.start+sound.duration-frame_number
        end,
        wait = function(frames_to_wait_for)
            scheduler.continue_script(
                frame_number+math.floor(frames_to_wait_for),
                coroutine.running())
            coroutine.yield()
        end,
        start_eruption_volcano = function()
            --obviously in a real game, this could 
            --affect some datastructure in a non-immediate way
            print(frame_number..": The volcano is erupting, BOOM!")
        end,
        start_camera_shake = function()
            print(frame_number..": SHAKY!")
        end,
        play = function(soundname)
            print(frame_number..": Playing: "..soundname)
            return {name = soundname, start = frame_number, duration = 30}
        end
    }
    

    还有游戏循环:

    while true do
        scheduler.run(frame_number,game_control)
        frame_number = frame_number+1
    end
    

    【讨论】:

      猜你喜欢
      • 2021-09-07
      • 1970-01-01
      • 2018-07-20
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-13
      • 1970-01-01
      相关资源
      最近更新 更多