2012-10-05 74 views
7

这个问题是一个好奇心点,作为下面两个程序之一工作。Perl线程中的垃圾回收

我使用Image :: Magick调整大小的照片。为了节省一点时间,我在每个照片的自己的线程中工作,并使用信号量来限制同时工作的线程数。本来我允许每个线程一次运行,但脚本会快速为所有照片分配3.5 GB(我只有2GB可用),并且由于所有交换到磁盘,脚本运行速度比正常情况慢5倍。

工作,信号灯版本的代码看起来是这样的:

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(); 
} 

这很快分配500MB,并很好地运行,而无需以往需要更多。 (线程是加入了相反的顺序提出一个观点。)

我想知道是否有可能与同时发动80个线程,并阻止他们大多是架空的,所以我改变了我的脚本来阻塞主线程:

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(); 
} 

该版本开始正常,但逐渐积累了原始版本使用的3.5GB空间。它比一次运行所有线程更快,但仍然比阻塞线程慢很多。

我的第一个猜测是线程所使用的内存在调用join()之前不会被释放,并且因为它是阻塞的主线程,所以在分配全部线程之前不会释放线程。但是,在第一个工作版本中,线程按照或多或少的随机顺序传递守卫,但以相反的顺序连接。如果我的猜测是正确的,那么比任何时候四个正在运行的线程都要等待join(),这个版本也应该更慢一些。

那么为什么这两个版本如此不同呢?

回答

3

您不需要创建超过4个线程。一个主要的好处是,这意味着76个Perl解释器的副本。而且,由于所有线程在大致相同的时间完成,它使得收割顺序相当没有意义。

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

接下来我要尝试排队。我只是好奇Perl中发生了什么,使信号量的一个版本完美工作,而且一个工作非常糟糕。 – pconley

+0

在您的版本中,只有解锁sem的线程才会使用大量内存。如果您在完成时收获它们,那意味着在任何给定时间只有4个线程正在使用大量内存。如果你最终只收获它们,80个线程最终会占用大量内存。 – ikegami