Lua协同程序甚至适用于什么?为什么这段代码不像我期望的那样工作?

时间:2011-02-26 17:04:07

标签: multithreading lua coroutine

我无法理解这段代码......我期待类似于线程的东西,我会得到一个随机的“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)

3 个答案:

答案 0 :(得分:51)

协同程序不是线程。

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

然而,在协同程序方面,你错过了更大的图景。查看维基百科的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函数)并在我离开的地方再次启动它。

答案 1 :(得分:14)

由于有很多评论询问如何实现使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

答案 2 :(得分:9)

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