2012-10-05 23 views
7

Esta pregunta es un punto de interés, ya que uno de los dos programas siguientes funciona.Recolección de basura en hilos Perl

Estoy usando Image :: Magick para cambiar el tamaño de una serie de fotos. Para ahorrar un poco de tiempo, trabajo en cada foto en su propio subproceso y uso un semáforo para limitar el número de subprocesos que funcionan simultáneamente. Originalmente, permitía que cada hilo se ejecutara a la vez, pero el script asignaba rápidamente 3,5 GB para todas las fotos (solo tengo 2GB disponibles), y el script se ejecutaba 5 veces más lento de lo normal debido a todo el intercambio en el disco.

El trabajo, código de la versión del semáforo se ve algo como esto:

use threads; 
use Thread::Semaphore; 
use Image::Magick; 

my $s = Thread::Semaphore->new(4); 
foreach (@photos) { 
    threads->create(\&launch_thread, $s); 
} 
foreach my $thr (reverse threads->list()) { 
    $thr->join(); 
} 

sub launch_thread { 
    my $s = shift; 
    $s->down(); 
    my $image = Image::Magick->new(); 

    # do memory-heavy work here 

    $s->up(); 
} 

Esto asigna rápidamente 500MB, y funciona bastante bien sin tener que requerir más. (Los hilos se unen en orden inverso para hacer un punto.)

me preguntaba si podría haber sobrecarga de lanzamiento de 80 hilos simultáneamente y bloquear la mayor parte de ellos, así que alteran mi script para bloquear el hilo principal:

my $s = Thread::Semaphore->new(4); 
foreach (@photos) { 
    $s->down(); 
    threads->create(\&launch_thread, $s); 
} 
foreach my $thr (threads->list()) { 
    $thr->join(); 
} 

sub launch_thread { 
    my $s = shift; 
    my $image = Image::Magick->new(); 

    # do memory-heavy work here 

    $s->up(); 
} 

Esta versión comienza bien, pero acumula gradualmente los 3.5GB de espacio que usaba la versión original. Es más rápido que ejecutar todos los hilos a la vez, pero aún bastante más lento que el bloqueo de hilos.

Mi primera suposición fue que la memoria utilizada por un hilo no se libera hasta que se invoca join(), y como es el hilo principal que bloquea, no se liberan hilos hasta que se hayan asignado todos. Sin embargo, en la primera versión que funciona, los hilos pasan al guardia en un orden más o menos aleatorio, pero se unen en orden inverso. Si mi conjetura es correcta, entonces, muchos más que los cuatro subprocesos en ejecución deberían estar esperando para unirse() ed en cualquier momento, y esta versión también debería ser más lenta.

¿Por qué estas dos versiones son tan diferentes?

Respuesta

3

No es necesario crear más de 4 hilos. Un beneficio importante es que esto significa 76 copias menos del intérprete de Perl. Además, hace que el orden de cosecha sea más bien discutible ya que todos los hilos terminan más o menos al mismo tiempo.

use threads; 
use Thread::Queue qw(); 
use Image::Magick qw(); 

use constant NUM_WORKERS => 4; 

sub process { 
    my ($photo) = @_; 
    ... 
} 

{ 
    my $request_q = Thread::Queue->new(); 

    my @threads; 
    for (1..NUM_WORKERS) { 
     push @threads, async { 
      while (my $photo = $request_q->dequeue()) { 
      process($photo); 
      } 
     }; 
    } 

    $request_q->enqueue($_) for @photos; 
    $request_q->enqueue(undef) for 1..NUM_THREADS; 
    $_->join() for @threads; 
} 
+0

Iba a intentar una cola a continuación. Solo tengo curiosidad por saber qué está pasando en Perl que hace que una versión del semáforo funcione perfectamente, y una funciona terriblemente. – pconley

+0

En su versión, solo los hilos que tienen que desbloquear el sem usan mucha memoria. Si los recoges mientras terminan, eso significa que solo 4 subprocesos están usando mucha memoria en un momento dado. Si solo los cosecha al final, 80 hilos eventualmente usan mucha memoria. – ikegami