2012-10-05 99 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