2012-11-18 165 views
3

我正在使用perl的threads模塊,它帶有一個簡單的搜尋器,我正在開發,因此我可以並行下載頁面。 Ocasionally,我得到錯誤信息像這樣的:perl線程異常退出

Thread 7 terminated abnormally: read timeout at /usr/lib64/perl5/threads.pm line 101. 
Thread 15 terminated abnormally: Can't connect to burgundywinecompany.com:80 (connect: timeout) at /usr/lib64/perl5/threads.pm line 101. 
Thread 19 terminated abnormally: write failed: Connection reset by peer at /usr/lib64/perl5/threads.pm line 101. 

當我線性運行該腳本無緒,我不會遇到這些錯誤。這些錯誤幾乎看起來像是來自LWP::UserAgent模塊,但他們似乎不應該導致線程異常退出。使用perl的線程時是否需要採取一些額外的預防措施?謝謝!

UPDATE:

我已經找到了這些異常終止的來源,它似乎是,每當我做使用LWP::UserAgent的請求。如果我刪除方法調用來下​​載網頁,則錯誤將停止。

示例腳本

下面的腳本會導致一個錯誤,我說的。最後瀏覽的網址就會超時,導致什麼應該僅僅是HTTP :: Repsonse對象的一部分,而不是導致線程異常終止:

#!/usr/bin/perl 
use threads; 
use Thread::Queue; 
use LWP::UserAgent; 

my $THREADS=10; # Number of threads 
          #(if you care about them) 
my $workq = Thread::Queue->new(); # Work to do 

my @stufftodo = qw(http://www.collectorsarmoury.com/ http://burgundywinecompany.com/ http://beetreeminiatures.com/); 

$workq->enqueue(@stufftodo); # Queue up some work to do 
$workq->enqueue("EXIT") for(1..$THREADS); # And tell them when 

threads->create("Handle_Work") for(1..$THREADS); # Spawn our workers 

$_->join for threads->list; 

sub Handle_Work { 
    while(my $todo=$workq->dequeue()) { 
     last if $todo eq 'EXIT'; # All done 
     print "$todo\n"; 
     my $ua = LWP::UserAgent->new; 
     my $RESP = $ua->get($todo); 
    } 
    threads->exit(0); 
} 
+0

您是否確保爲每個線程獲取所有內容的新實例?審覈代碼以確保每個線程中都沒有共享,每個線程都需要初始化自己的perl對象,很少應該傳入(比如只有URL),並且不應該訪問共享的全局變量。我懷疑這個問題只是設計錯誤。 –

+0

@DarrylMiles,我已經發布了上面的示例腳本,導致錯誤。我很確定在這個腳本文件中沒有共享任何東西,但我仍然得到錯誤。 – srchulo

+0

好的腳本至少我們可以看到你在做什麼。你只有4個項目放置在$ workq中,但是你啓動了10個線程,每個線程可以在多個項目上工作。第四個線程不可能看到任何工作要做。目標網站是由您擁有/管理的嗎?你怎麼知道他們沒有連接氾濫控制?原始錯誤中的線程編號指示超過10個(如果它們是連續的)。可以將子線程添加到當前線程啓動開始/停止以及所有工作的總數中,也會在所有輸出中發出threads-> tid()。也許你更好地看問題。 –

回答

3

我打了一下與你的源以及與此想出了:

#!/usr/bin/perl 

use 5.012; use warnings; 
use threads; use Thread::Queue; use LWP::UserAgent; 

use constant THREADS => 10; 

my $queue = Thread::Queue->new(); 
my @URLs = qw(http://www.collectorsarmoury.com/ 
       http://burgundywinecompany.com/ 
       http://beetreeminiatures.com/  ); 
my @threads; 

for (1..THREADS) { 
    push @threads, threads->create(sub { 
     my $ua = LWP::UserAgent->new; 
     $ua->timeout(5); # short timeout for easy testing. 
     while(my $task = $queue->dequeue) { 
      my $response = eval{ $ua->get($task)->status_line }; 
      say "$task --> $response"; 
     } 
    }); 
} 

$queue->enqueue(@URLs); 
$queue->enqueue(undef) for 1..THREADS; 
# ... here work is done 
$_->join foreach @threads; 

輸出:

http://www.collectorsarmoury.com/ --> 200 OK 
http://burgundywinecompany.com/ --> 200 OK 
http://beetreeminiatures.com/ --> 500 Can't connect to beetreeminiatures.com:80 (timeout) 

輸出而不eval

http://www.collectorsarmoury.com/ --> 200 OK 
http://burgundywinecompany.com/ --> 200 OK 
http://beetreeminiatures.com/ --> 500 Can't connect to beetreeminiatures.com:80 (timeout) 
Thread 2 terminated abnormally: Can't connect to beetreeminiatures.com:80 (timeout) 

LWP::Protocol::http::Socket: connect: timeout at /usr/share/perl5/LWP/Protocol/http.pm line 51. 

東西,但我不同的是:

不重要:

  • 我不exit我的線程;我只是在結尾(隱含return
  • 我爲每個線程分配一個用戶代理,而不是每個請求一個。

更好的風格:

  • 我用undef信號線程終止:一旦值出列,循環條件是無論如何假,線程終止。如果你想傳遞一個特殊的字符串來終止信號,你應該循環使用while (1),並在循環體內出隊。

重要:

  • 要消除這些討厭的錯誤,我eval倒是在get。如果請求die,我的線程不會跟風而是保持冷靜並繼續。

因爲get可能導致死亡。如果我們查看source of LWP::Protocol::http的第51行,我們看到如果沒有爲連接創建套接字,將會引發致命錯誤。當主機名無法解析時,可能會發生這種情況。

在我的代碼中,我決定忽略錯誤(因爲我已經打印狀態行)。根據問題,您可能需要重試該URL,或者提供更多信息的警告。查看鏈接的源代碼以獲取錯誤處理的一個很好的示例。

不幸的是,我無法重現您的確切錯誤(警告中給出的行指向threads->exit()類方法)。但在大多數情況下,使用eval應防止異常終止。

+0

太棒了!完美的作品。非常感謝 :) – srchulo

0

好Perl並有一個機制來中止並做致命的()。但我不認爲這是你的情況。

如果你看一下threads.pl第101行,這可能是線程退出方法和使用非零退出狀態可能被認爲是一個異常情況。

我認爲這些東西是無害的,'異常終止'的使用只是表明手術不是100%成功。這意味着您應該爲那些操作未完成的線程規劃和實施恢復方案。

對你來說,單詞的選擇是令人擔憂和引起擔憂的,但是如果你將消息改爲:「線程123沒有完成指示成功」,它可能看起來不那麼令人震驚,更符合這種情況。

允許線程main方法返回(如果需要,在途中釋放數據)也更好。這不是使用threads :: exit,除非當做main方法中的最後一件事情。

關於分叉,你是否聲稱它在分叉時永遠不會失敗,並且分叉的過程是否表示非零'退出狀態'失敗。當你使用線程時,你也確定你沒有超載網站,代理,網絡等等。

+0

嗯......但是如果我線性地下載URL或者如果我通過分叉來完成,這個錯誤信息是不會被給出的。沒有辦法阻止終止線程? – srchulo

+0

是的,分叉過程不會不規律地失敗。而且我的網站的代理/網絡/等也不太可能,因爲我的測試數據中的每個網址都位於不同的網站。不,分岔並不表示失敗時出現非零退出狀態。它正常結束。 – srchulo

+0

如果您確定,可能是任何東西,程序錯誤,設計錯誤,perl錯誤。你需要深究。 –

2

它看起來像get方法是設置[email protected],即使它不die。你可以看到它是不會死的,把一些印刷品的get後:

my $RESP = $ua->get($todo); 
if($RESP->is_success) { 
    print "$todo success\n"; 
} else { 
    print "$todo failed: ".$RESP->status_line."\n"; 
} 

你可以看到打印失敗後請求的線程退出之前仍然發生:

http://www.collectorsarmoury.com/ success 
http://burgundywinecompany.com/ success 
http://beetreeminiatures.com/ failed: 500 Can't connect to beetreeminiatures.com:80 (Connection timed out) 
Thread 3 terminated abnormally: Can't connect to beetreeminiatures.com:80 (Connection timed out) 

線程退出然後在[email protected]被設置爲異常時出現拾取。如果您在退出線程之前重置[email protected](或local [email protected]Handle_Workeval圍繞get),則線程乾淨地退出。

相關問題