2011-02-26 16 views
26

Tengo problemas para entender este código ... Esperaba algo similar a un subprocesamiento en el que obtuviera una salida con "nooo" y "yaaaay" aleatorios intercalados entre sí, ya que ambos imprimían de forma asincrónica, pero Descubrí que el hilo principal parece bloquearse en la primera llamada de coroutine.resume() y por lo tanto impide que el siguiente se inicie hasta que el primero haya cedido.¿Para qué sirven corutinas Lua? ¿Por qué este código no funciona como lo espero?

Si estas son las operaciones previstas, ¿para qué sirven y cómo lograré el objetivo que esperaba? ¿Tendría que implementar mi propio programador para que estas corutinas operen de manera asíncrona ?, porque eso parece desordenado, ¡y también puedo usar funciones!

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) 
+6

Coroutines no son hilos. Son como hilos, en el sentido de que son entornos de ejecución separados, pero su programa es el programador, y no son preventivos. Agregaría una respuesta completa, pero mi Lua está oxidada, así que no puedo proporcionar ningún ejemplo de código ... –

Respuesta

46

Coroutines no son hilos.

Coroutines son como hilos que nunca se programan activamente. Entonces, sí, es cierto que tendría que escribir su propio programador para que ambas corutinas se ejecuten simultáneamente.

Sin embargo, le falta una visión más amplia cuando se trata de coroutines. Echa un vistazo a list of coroutine uses de wikipedia. Aquí hay un ejemplo concreto que podría guiarlo en la dirección correcta.

-- 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 

El script anterior se podría escribir para funcionar de forma iterativa con una sentencia switch y algunas variables de estado inteligentes. Pero es mucho más claro cuando se escribe como corutina. El script anterior podría ser un hilo, pero ¿realmente necesita dedicar un hilo de kernel a este código simple? Un nivel de juego ocupado podría tener 100 de estas corrutinas sin afectar el rendimiento. Sin embargo, si cada uno de estos fuera un hilo, podría salirse con 20-30 antes de que el rendimiento empezara a deteriorarse.

Una coroutine está diseñada para permitirme escribir código que almacena el estado en la pila para que pueda dejar de ejecutarlo por un tiempo (las funciones wait) y volver a iniciarlo donde lo dejé.

+1

Gracias, ¡esto responde muy bien a mis preguntas! – kellpossible

+0

Perfecto. Me preguntaba cómo podría hacer esto en LUA, y ahora sé qué buscar :). Gracias. – dcousens

+1

@deft Solo para ayudar a aclarar, ¿cómo funciona el ejemplo anterior? Como entiendo, lua tiene 3 funciones principales para soportar corutinas: 'create',' resume' y 'yield'. afaik 'wait' no es una función lua incorporada. Entonces, ¿'espera' implementado por usted? ¿Dónde ocurre el rendimiento? ¿Cómo se asegura de que este corumna 'resume' después de la * x * cantidad de tiempo que ha pasado desde el último 'rendimiento'? – greatwolf

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 
8

Dado que ha habido una serie de comentarios preguntando cómo implementar la función wait que haría ejemplo de trabajo deft_code 's, he decidido escribir una posible implementación. La idea general es que tenemos un programador con una lista de corutinas, y el planificador decide cuándo devolver el control a las corutinas después de que ceden el control con sus llamadas wait. Esto es deseable porque hace que el código asíncrono sea legible y fácil de razonar.

Que este es solo un posible uso de corutinas, es una herramienta de abstracción más general que puede usarse para muchos propósitos diferentes (como escribir iteradores y generadores, escribir objetos de procesamiento de flujo con estado (por ejemplo, varias etapas en un analizador), implementando excepciones y continuaciones, etc.).

En primer lugar: la definición planificador:

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 

Ahora, inicializar el mundo:

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)) 

El (ficticio) de interfaz para el juego:

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 
} 

Y el bucle del juego :

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