2010-03-03 6 views
5

Necesito cargar un grupo de archivos en un directorio a S3. Dado que más del 90% del tiempo requerido para cargar se gasta en esperar que termine la solicitud http, quiero ejecutar varios de ellos a la vez de alguna manera.Cómo usar las fibras de rubí para evitar el bloqueo IO

¿Pueden las Fibras ayudarme con esto en absoluto? Se describen como una forma de resolver este tipo de problema, pero no puedo pensar en ninguna forma en que pueda hacer ningún trabajo mientras bloquea una llamada http.

¿De alguna manera puedo resolver este problema sin hilos?

+0

Entonces, ¿alguien puede comentar acerca de las fibras? ¿Estoy en lo cierto al suponer que las fibras no tienen poderes de "hacer cosas en el fondo"? –

Respuesta

3

No estoy arriba en fibras de 1,9, pero Hilos regulares desde 1.8.6 puede resolver este problema. Intente utilizar una cola http://ruby-doc.org/stdlib/libdoc/thread/rdoc/classes/Queue.html

Mirando el ejemplo en la documentación, su consumidor es la parte que realiza la carga. 'Consume' una URL y un archivo, y carga los datos. El productor es la parte de su programa que sigue trabajando y encuentra nuevos archivos para cargar.

Si desea cargar varios archivos a la vez, sólo tiene que iniciar un nuevo hilo para cada archivo:

t = Thread.new do 
    upload_file(param1, param2) 
end 
@all_threads << t 

Luego, más adelante en el código 'productor' (que, recordemos, no tiene por qué estar en su propio hilo, podría ser el programa principal):

@all_threads.each do |t| 
    t.join if t.alive? 
end 

la cola puede ser un o una @member_variable $ mundial.

+1

Aunque mi fibra questino parece haber quedado sin respuesta –

1

Usted podría utilizar procesos separados para este lugar de hilos:

#!/usr/bin/env ruby 

$stderr.sync = true 

# Number of children to use for uploading 
MAX_CHILDREN = 5 

# Hash of PIDs for children that are working along with which file 
# they're working on. 
@child_pids = {} 

# Keep track of uploads that failed 
@failed_files = [] 

# Get the list of files to upload as arguments to the program 
@files = ARGV 


### Wait for a child to finish, adding the file to the list of those 
### that failed if the child indicates there was a problem. 
def wait_for_child 
    $stderr.puts " waiting for a child to finish..." 
    pid, status = Process.waitpid2(0) 
    file = @child_pids.delete(pid) 
    @failed_files << file unless status.success? 
end 


### Here's where you'd put the particulars of what gets uploaded and 
### how. I'm just sleeping for the file size in bytes * milliseconds 
### to simulate the upload, then returning either +true+ or +false+ 
### based on a random factor. 
def upload(file) 
    bytes = File.size(file) 
    sleep(bytes * 0.00001) 
    return rand(100) > 5 
end 


### Start a child uploading the specified +file+. 
def start_child(file) 
    if pid = Process.fork 
     $stderr.puts "%s: uploaded started by child %d" % [ file, pid ] 
     @child_pids[ pid ] = file 
    else 
     if upload(file) 
      $stderr.puts "%s: done." % [ file ] 
      exit 0 # success 
     else 
      $stderr.puts "%s: failed." % [ file ] 
      exit 255 
     end 
    end 
end 


until @files.empty? 

    # If there are already the maximum number of children running, wait 
    # for one to finish 
    wait_for_child() if @child_pids.length >= MAX_CHILDREN 

    # Start a new child working on the next file 
    start_child(@files.shift) 

end 


# Now we're just waiting on the final few uploads to finish 
wait_for_child() until @child_pids.empty? 

if @failed_files.empty? 
    exit 0 
else 
    $stderr.puts "Some files failed to upload:", 
     @failed_files.collect {|file| " #{file}" } 
    exit 255 
end 
2

para responder a sus preguntas reales:

Puede Fibras ayudarme con esto en absoluto?

No, no pueden. Jörg W Mittag explains why best.

No, no puede hacer concurrencia con Fibras. Las fibras simplemente no son una construcción de concurrencia, son una construcción de flujo de control, como Excepciones. Ese es el objetivo de Fibras: nunca se ejecutan en paralelo, son cooperativos y deterministas. Las fibras son corutinas. (De hecho, nunca entendí por qué no se llaman simplemente Coroutines.)

La única construcción de concurrencia en Ruby es Thread.

Cuando dice que la única construcción de concurrencia en Ruby es Thread, recuerde que hay muchas implementaciones diferentes de Ruby y que varían en sus implementaciones de threading. Jörg una vez más provides a great answer a estas diferencias; y concluye correctamente que solo algo como JRuby (que usa subprocesos de JVM asignados a subprocesos nativos) o bifurcando su proceso es cómo puede lograr un paralelismo verdadero.

¿De alguna manera puedo resolver este problema sin hilos?

Aparte de bifurcar el proceso, me gustaría también sugieren que nos fijamos en EventMachine y algo así como em-http-request. Es un cliente HTTP basado en eventos, sin bloqueo, reactor pattern que es asíncrono y no incurre en la sobrecarga de subprocesos.

+0

Consulte Sincronización con la interfaz múltiple en https://github.com/igrigorik/em-http-request/wiki/Parallel-Requests – Sairam

1

Aaron Patterson (@tenderlove) utiliza un ejemplo casi exactamente como la suya para describir exactamente por qué puede y debe hilos de uso para lograr la concurrencia en su situación.

La mayoría de las bibliotecas de E/S ahora son lo suficientemente inteligentes como para liberar el GVL (Global VM Lock, o la mayoría de la gente lo conoce como GIL o Global Interpreter Lock) cuando se hace IO. Hay una llamada a función simple en C para hacer esto. No necesita preocuparse por el código C, pero para usted esto significa que la mayoría de las bibliotecas IO que valen la pena liberarán el GVL y permitirán que se ejecuten otros subprocesos mientras que el subproceso que está haciendo el IO espera a que los datos vuelvan .

Si lo que acabo de decir es confuso, no tiene por qué preocuparse demasiado. Lo principal que necesita saber es que si está utilizando una biblioteca decente para hacer sus solicitudes HTTP (o cualquier otra operación de E/S para ese asunto ... base de datos, comunicación entre procesos, lo que sea), el intérprete de Ruby (MRI) es lo suficientemente inteligente como para poder liberar el bloqueo en el intérprete y permitir que se ejecuten otros subprocesos, mientras que un subproceso espera que IO regrese. Si el siguiente hilo tiene su propio IO para agarrar, el intérprete de Ruby hará lo mismo (suponiendo que la biblioteca de IO está construida para utilizar esta característica de Ruby, que creo que la mayoría son actualmente).

Por lo tanto, para resumir lo que estoy diciendo, ¡use hilos! Debería ver el beneficio de rendimiento. De lo contrario, verifique si su biblioteca http está utilizando la función rb_thread_blocking_region() en C y, de no ser así, descubra por qué no. Tal vez hay una buena razón, quizás deba considerar usar una mejor biblioteca.

El enlace al video Aaron Patterson está aquí: http://www.youtube.com/watch?v=kufXhNkm5WU

Vale la pena un reloj, aunque sólo sea por las risas, como Aaron Patterson es una de las personas más divertidas en Internet.

Cuestiones relacionadas